From d1995d74fe9400e60f92ff350bc6e699fcf85be4 Mon Sep 17 00:00:00 2001 From: chenkins Date: Fri, 6 Dec 2024 16:43:55 +0100 Subject: [PATCH 01/39] First ideas for uvf imple. --- .../core/cryptomator/CryptoVault.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) 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..2fe2b9d9053 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java @@ -76,6 +76,10 @@ /** * Cryptomator vault implementation */ +// 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 implements Vault { private static final Logger log = LogManager.getLogger(CryptoVault.class); @@ -114,6 +118,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 +126,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 +140,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()) { @@ -219,6 +229,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 +246,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 +256,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 +269,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 +277,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; @@ -316,6 +331,7 @@ public synchronized void close() { 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: @@ -334,10 +350,15 @@ protected CryptoDirectory createDirectoryProvider(final VaultConfig vaultConfig) } } + // UVF: extract to v6/v7/uvf imple, VaultConfig only for v6/v7 + // pro memoria: + // create -> open + // unlock -> open protected void open(final VaultConfig vaultConfig, final CharSequence passphrase) throws BackgroundException { this.open(vaultConfig, passphrase, this.createFilenameProvider(vaultConfig), this.createDirectoryProvider(vaultConfig)); } + // UVF: extract to v6/v7/uvf, at this point we know which version protected void open(final VaultConfig vaultConfig, final CharSequence passphrase, final CryptoFilename filenameProvider, final CryptoDirectory directoryProvider) throws BackgroundException { try { @@ -352,10 +373,12 @@ protected void open(final VaultConfig vaultConfig, final CharSequence passphrase } } + // UVF: unused?! protected void open(final VaultConfig vaultConfig, final Masterkey masterKey) throws BackgroundException { this.open(vaultConfig, masterKey, this.createFilenameProvider(vaultConfig), this.createDirectoryProvider(vaultConfig)); } + // UVF: extract to v6/v7 imple, can we use the new MasterKey interface from https://github.com/cryptomator/cryptolib/pull/51/files? protected void open(final VaultConfig vaultConfig, final Masterkey masterKey, final CryptoFilename filenameProvider, final CryptoDirectory directoryProvider) throws BackgroundException { this.vaultVersion = vaultConfig.version; @@ -403,6 +426,7 @@ public Path encrypt(final Session session, final Path file, boolean metadata) return this.encrypt(session, file, file.attributes().getDirectoryId(), metadata); } + // UVF: extract to delegate? public Path encrypt(final Session session, final Path file, final String directoryId, boolean metadata) throws BackgroundException { final Path encrypted; if(file.isFile() || metadata) { From dee75a2514f637adbb86c2087cfcc6dcceb2d048 Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Mon, 6 Jan 2025 10:55:54 +0200 Subject: [PATCH 02/39] Switch to uvf draft snapshot dependency. --- cryptomator/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptomator/pom.xml b/cryptomator/pom.xml index 8875a0ac066..0b5a3fe4a80 100644 --- a/cryptomator/pom.xml +++ b/cryptomator/pom.xml @@ -25,7 +25,7 @@ jar - 2.1.2.1 + 2.3.0-uvfdraft-SNAPSHOT From 61000a28cd2f18c52da97a6473d86f9a2fc2944a Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Sat, 18 Jan 2025 11:17:30 +0100 Subject: [PATCH 03/39] Migrate API changes. --- .../ch/cyberduck/core/cryptomator/CryptoVault.java | 11 ++++++----- .../cyberduck/core/cryptomator/CryptoVaultTest.java | 4 ++-- .../SFTPCryptomatorInteroperabilityTest.java | 3 ++- 3 files changed, 10 insertions(+), 8 deletions(-) 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 2fe2b9d9053..89972f09b47 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java @@ -45,6 +45,7 @@ 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; @@ -160,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 { @@ -362,7 +363,7 @@ protected void open(final VaultConfig vaultConfig, final CharSequence passphrase 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); + final PerpetualMasterkey masterKey = this.getMasterKey(vaultConfig.getMkfile(), passphrase); this.open(vaultConfig, masterKey, filenameProvider, directoryProvider); } catch(IllegalArgumentException | IOException e) { @@ -374,12 +375,12 @@ protected void open(final VaultConfig vaultConfig, final CharSequence passphrase } // UVF: unused?! - protected void open(final VaultConfig vaultConfig, final Masterkey masterKey) throws BackgroundException { + protected void open(final VaultConfig vaultConfig, final PerpetualMasterkey masterKey) throws BackgroundException { this.open(vaultConfig, masterKey, this.createFilenameProvider(vaultConfig), this.createDirectoryProvider(vaultConfig)); } // UVF: extract to v6/v7 imple, can we use the new MasterKey interface from https://github.com/cryptomator/cryptolib/pull/51/files? - protected void open(final VaultConfig vaultConfig, final Masterkey masterKey, + protected void open(final VaultConfig vaultConfig, final PerpetualMasterkey masterKey, final CryptoFilename filenameProvider, final CryptoDirectory directoryProvider) throws BackgroundException { this.vaultVersion = vaultConfig.version; final CryptorProvider provider = CryptorProvider.forScheme(vaultConfig.getCipherCombo()); @@ -392,7 +393,7 @@ protected void open(final VaultConfig vaultConfig, final Masterkey masterKey, 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( 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/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPCryptomatorInteroperabilityTest.java b/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPCryptomatorInteroperabilityTest.java index 0dac54678ba..58a14ea9a7d 100644 --- a/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPCryptomatorInteroperabilityTest.java +++ b/ssh/src/test/java/ch/cyberduck/core/cryptomator/SFTPCryptomatorInteroperabilityTest.java @@ -54,6 +54,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; @@ -99,7 +100,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); From 010a4b932c2a47468eae52d77cc4e1f9214ce9d8 Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Sat, 18 Jan 2025 19:42:34 +0100 Subject: [PATCH 04/39] Extract filename cryptor and provider. --- .../core/cryptomator/CryptoVault.java | 29 +--- .../cyberduck/core/cryptomator/UVFVault.java | 136 ++++++++++++++++++ .../impl/CryptoDirectoryV6Provider.java | 19 +-- .../impl/CryptoDirectoryV7Provider.java | 21 +-- .../impl/CryptoDirectoryV6ProviderTest.java | 74 +++------- .../impl/CryptoDirectoryV7ProviderTest.java | 78 +++------- 6 files changed, 201 insertions(+), 156 deletions(-) create mode 100644 cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java 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 89972f09b47..595c3629e7c 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java @@ -342,29 +342,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 imple, VaultConfig only for v6/v7 - // pro memoria: - // create -> open - // unlock -> open - protected void open(final VaultConfig vaultConfig, final CharSequence passphrase) throws BackgroundException { - this.open(vaultConfig, passphrase, this.createFilenameProvider(vaultConfig), this.createDirectoryProvider(vaultConfig)); - } - // UVF: extract to v6/v7/uvf, at this point we know which version - protected void open(final VaultConfig vaultConfig, final CharSequence passphrase, - final CryptoFilename filenameProvider, final CryptoDirectory directoryProvider) throws BackgroundException { + protected void open(final VaultConfig vaultConfig, final CharSequence passphrase) throws BackgroundException { try { final PerpetualMasterkey masterKey = this.getMasterKey(vaultConfig.getMkfile(), passphrase); - this.open(vaultConfig, masterKey, filenameProvider, directoryProvider); + this.open(vaultConfig, masterKey); } catch(IllegalArgumentException | IOException e) { throw new VaultException("Failure reading key file", e); @@ -376,20 +367,14 @@ protected void open(final VaultConfig vaultConfig, final CharSequence passphrase // UVF: unused?! protected void open(final VaultConfig vaultConfig, final PerpetualMasterkey masterKey) throws BackgroundException { - this.open(vaultConfig, masterKey, this.createFilenameProvider(vaultConfig), this.createDirectoryProvider(vaultConfig)); - } - - // UVF: extract to v6/v7 imple, can we use the new MasterKey interface from https://github.com/cryptomator/cryptolib/pull/51/files? - protected void open(final VaultConfig vaultConfig, final PerpetualMasterkey masterKey, - final CryptoFilename filenameProvider, final CryptoDirectory directoryProvider) 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(); } 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..6f7803728d6 --- /dev/null +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -0,0 +1,136 @@ +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.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.features.Vault; +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.UVFMasterkey; + +import java.util.EnumSet; + +public class UVFVault implements Vault { + + 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; + + 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 { + return null; + } + + // 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); + //TODO where? this.nonceSize = vaultConfig.getNonceSize(); + return this; + } + + @Override + public void close() { + + } + + @Override + public boolean contains(final Path file) { + return false; + } + + @Override + public Path encrypt(final Session session, final Path file) throws BackgroundException { + return null; + } + + @Override + public Path encrypt(final Session session, final Path file, final boolean metadata) throws BackgroundException { + return null; + } + + @Override + public Path decrypt(final Session session, final Path file) throws BackgroundException { + return null; + } + + @Override + public long toCiphertextSize(final long cleartextFileOffset, final long cleartextFileSize) { + return 0; + } + + @Override + public long toCleartextSize(final long cleartextFileOffset, final long ciphertextFileSize) throws BackgroundException { + return 0; + } + + @Override + public T getFeature(final Session session, final Class type, final T delegate) { + return null; + } + + @Override + public State getState() { + return null; + } + + @Override + public Path getHome() { + return null; + } +} 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/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)); From 007d66da96ac682854149ec6bda6549e55c23185 Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Mon, 20 Jan 2025 09:21:57 +0100 Subject: [PATCH 05/39] Intermediate interface for Cryptomator stuff. --- .../cryptomator/CryptoTransferStatus.java | 4 +- .../core/cryptomator/CryptoVault.java | 21 ++++--- .../cryptomator/CryptoVaultInterface.java | 59 +++++++++++++++++++ .../features/CryptoBulkFeature.java | 6 +- .../features/CryptoChecksumCompute.java | 8 +-- .../features/CryptoCopyFeature.java | 6 +- .../features/CryptoDeleteV6Feature.java | 6 +- .../features/CryptoDeleteV7Feature.java | 6 +- .../features/CryptoDirectoryV6Feature.java | 6 +- .../features/CryptoDirectoryV7Feature.java | 6 +- .../features/CryptoDownloadFeature.java | 7 +-- .../features/CryptoEncryptionFeature.java | 3 +- .../features/CryptoMoveV6Feature.java | 6 +- .../features/CryptoMoveV7Feature.java | 6 +- .../features/CryptoMultipartWriteFeature.java | 6 +- .../features/CryptoReadFeature.java | 6 +- .../features/CryptoTimestampFeature.java | 6 +- .../features/CryptoTouchFeature.java | 6 +- .../features/CryptoUploadFeature.java | 6 +- .../features/CryptoWriteFeature.java | 6 +- 20 files changed, 121 insertions(+), 65 deletions(-) create mode 100644 cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVaultInterface.java 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..34da1e17f5b 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 CryptoVaultInterface vault; - public CryptoTransferStatus(final CryptoVault vault, final TransferStatus proxy) { + public CryptoTransferStatus(final CryptoVaultInterface 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 595c3629e7c..155574a72bb 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java @@ -81,7 +81,7 @@ // - 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 implements Vault { +public class CryptoVault implements CryptoVaultInterface { private static final Logger log = LogManager.getLogger(CryptoVault.class); public static final int VAULT_VERSION_DEPRECATED = 6; @@ -402,16 +402,6 @@ public boolean contains(final Path file) { return false; } - @Override - public Path encrypt(final Session session, final Path file) throws BackgroundException { - return this.encrypt(session, file, file.attributes().getDirectoryId(), false); - } - - @Override - public Path encrypt(final Session session, final Path file, boolean metadata) throws BackgroundException { - return this.encrypt(session, file, file.attributes().getDirectoryId(), metadata); - } - // UVF: extract to delegate? public Path encrypt(final Session session, final Path file, final String directoryId, boolean metadata) throws BackgroundException { final Path encrypted; @@ -594,38 +584,47 @@ public Path getHome() { return home; } + @Override public Path getMasterkey() { return masterkey; } + @Override public Path getConfig() { return config; } + @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 int getNonceSize() { return nonceSize; } + @Override public int numberOfChunks(final long cleartextFileSize) { return (int) (cleartextFileSize / cryptor.fileContentCryptor().cleartextChunkSize() + ((cleartextFileSize % cryptor.fileContentCryptor().cleartextChunkSize() > 0) ? 1 : 0)); diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVaultInterface.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVaultInterface.java new file mode 100644 index 00000000000..83116b1fae6 --- /dev/null +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVaultInterface.java @@ -0,0 +1,59 @@ +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.Path; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.Vault; + +import org.cryptomator.cryptolib.api.FileContentCryptor; +import org.cryptomator.cryptolib.api.FileHeaderCryptor; + +public interface CryptoVaultInterface extends Vault { + + Path getMasterkey(); + + Path getConfig(); + + FileHeaderCryptor getFileHeaderCryptor(); + + FileContentCryptor getFileContentCryptor(); + + CryptorCache getFileNameCryptor(); + + CryptoFilename getFilenameProvider(); + + CryptoDirectory getDirectoryProvider(); + + int getNonceSize(); + + int numberOfChunks(long cleartextFileSize); + + long toCleartextSize(final long cleartextFileOffset, final long ciphertextFileSize) throws CryptoInvalidFilesizeException; + + @Override + default Path encrypt(Session session, Path file) throws BackgroundException { + return this.encrypt(session, file, file.attributes().getDirectoryId(), false); + } + + @Override + default Path encrypt(Session session, Path file, boolean metadata) throws BackgroundException { + return this.encrypt(session, file, file.attributes().getDirectoryId(), metadata); + } + + Path encrypt(Session session, Path file, String directoryId, boolean metadata) throws BackgroundException; +} 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..8c866c954d6 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.CryptoVaultInterface; 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 CryptoVaultInterface 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 CryptoVaultInterface 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..00c98ee147b 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 @@ -16,7 +16,7 @@ */ import ch.cyberduck.core.cryptomator.CryptoOutputStream; -import ch.cyberduck.core.cryptomator.CryptoVault; +import ch.cyberduck.core.cryptomator.CryptoVaultInterface; 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 CryptoVaultInterface cryptomator; private final ChecksumCompute delegate; - public CryptoChecksumCompute(final ChecksumCompute delegate, final CryptoVault vault) { - this.cryptomator = vault; + public CryptoChecksumCompute(final ChecksumCompute delegate, final CryptoVaultInterface 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 dbff373f7a8..801f5a878d1 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.CryptoVaultInterface; import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator; import ch.cyberduck.core.cryptomator.random.RotatingNonceGenerator; import ch.cyberduck.core.exception.BackgroundException; @@ -37,11 +37,11 @@ public class CryptoCopyFeature implements Copy { private final Session session; private final Copy proxy; - private final CryptoVault vault; + private final CryptoVaultInterface vault; private Session target; - public CryptoCopyFeature(final Session session, final Copy proxy, final CryptoVault vault) { + public CryptoCopyFeature(final Session session, final Copy proxy, final CryptoVaultInterface 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 01a3ce06cf9..aa0ab7a695c 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 @@ -21,7 +21,7 @@ import ch.cyberduck.core.Path; import ch.cyberduck.core.Session; import ch.cyberduck.core.cryptomator.CryptoFilename; -import ch.cyberduck.core.cryptomator.CryptoVault; +import ch.cyberduck.core.cryptomator.CryptoVaultInterface; 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 CryptoVaultInterface 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 CryptoVaultInterface 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 8d8570ef479..7617eb8f143 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,7 +21,7 @@ import ch.cyberduck.core.Path; import ch.cyberduck.core.Session; import ch.cyberduck.core.cryptomator.CryptoFilename; -import ch.cyberduck.core.cryptomator.CryptoVault; +import ch.cyberduck.core.cryptomator.CryptoVaultInterface; import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryV7Provider; import ch.cyberduck.core.exception.AccessDeniedException; import ch.cyberduck.core.exception.BackgroundException; @@ -45,10 +45,10 @@ public class CryptoDeleteV7Feature implements Delete, Trash { private final Session session; private final Delete proxy; - private final CryptoVault vault; + private final CryptoVaultInterface 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 CryptoVaultInterface 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 f2eb8650ff8..1e63d0a5cab 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 @@ -20,7 +20,7 @@ import ch.cyberduck.core.Session; import ch.cyberduck.core.UUIDRandomStringService; import ch.cyberduck.core.cryptomator.ContentWriter; -import ch.cyberduck.core.cryptomator.CryptoVault; +import ch.cyberduck.core.cryptomator.CryptoVaultInterface; 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 CryptoVaultInterface 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 CryptoVaultInterface 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 ebf3e295402..85c9df2e62f 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 @@ -20,7 +20,7 @@ import ch.cyberduck.core.Session; import ch.cyberduck.core.UUIDRandomStringService; import ch.cyberduck.core.cryptomator.ContentWriter; -import ch.cyberduck.core.cryptomator.CryptoVault; +import ch.cyberduck.core.cryptomator.CryptoVaultInterface; 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 CryptoVaultInterface 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 CryptoVaultInterface 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..20386818dba 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.CryptoVaultInterface; 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 CryptoVaultInterface 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 CryptoVaultInterface 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 e43afc0965d..0a54e29ad8b 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.CryptoVaultInterface; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.InvalidFilenameException; import ch.cyberduck.core.features.Delete; @@ -34,9 +34,9 @@ public class CryptoMoveV6Feature implements Move { private final Session session; private final Move proxy; - private final CryptoVault vault; + private final CryptoVaultInterface vault; - public CryptoMoveV6Feature(final Session session, final Move delegate, final CryptoVault cryptomator) { + public CryptoMoveV6Feature(final Session session, final Move delegate, final CryptoVaultInterface 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 a0bd1c8eb7f..225a086ef6b 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.CryptoVaultInterface; import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryV7Provider; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.InvalidFilenameException; @@ -35,9 +35,9 @@ public class CryptoMoveV7Feature implements Move { private final Session session; private final Move proxy; - private final CryptoVault vault; + private final CryptoVaultInterface vault; - public CryptoMoveV7Feature(final Session session, final Move delegate, final CryptoVault cryptomator) { + public CryptoMoveV7Feature(final Session session, final Move delegate, final CryptoVaultInterface 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..f3f9f14ea2c 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.CryptoVaultInterface; 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 CryptoVaultInterface 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 CryptoVaultInterface 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 758f6ee2db0..ea62bbd07af 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 @@ -20,7 +20,7 @@ import ch.cyberduck.core.Path; import ch.cyberduck.core.Session; import ch.cyberduck.core.cryptomator.CryptoInputStream; -import ch.cyberduck.core.cryptomator.CryptoVault; +import ch.cyberduck.core.cryptomator.CryptoVaultInterface; 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 CryptoVaultInterface vault; - public CryptoReadFeature(final Session session, final Read proxy, final CryptoVault vault) { + public CryptoReadFeature(final Session session, final Read proxy, final CryptoVaultInterface 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..70506559f95 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 @@ -18,7 +18,7 @@ import ch.cyberduck.core.Path; import ch.cyberduck.core.Session; import ch.cyberduck.core.cryptomator.CryptoTransferStatus; -import ch.cyberduck.core.cryptomator.CryptoVault; +import ch.cyberduck.core.cryptomator.CryptoVaultInterface; 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 CryptoVaultInterface vault; - public CryptoTimestampFeature(final Session session, final Timestamp proxy, final CryptoVault vault) { + public CryptoTimestampFeature(final Session session, final Timestamp proxy, final CryptoVaultInterface 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 238adb433b9..0695127c993 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.CryptoVaultInterface; 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 CryptoVaultInterface 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 CryptoVaultInterface 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..7beecee0570 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 @@ -21,7 +21,7 @@ import ch.cyberduck.core.ProgressListener; import ch.cyberduck.core.Session; import ch.cyberduck.core.cryptomator.CryptoTransferStatus; -import ch.cyberduck.core.cryptomator.CryptoVault; +import ch.cyberduck.core.cryptomator.CryptoVaultInterface; 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 CryptoVaultInterface 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 CryptoVaultInterface 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 7a8ea6f211b..9013f7a86eb 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 @@ -21,7 +21,7 @@ import ch.cyberduck.core.Session; import ch.cyberduck.core.cryptomator.CryptoOutputStream; import ch.cyberduck.core.cryptomator.CryptoTransferStatus; -import ch.cyberduck.core.cryptomator.CryptoVault; +import ch.cyberduck.core.cryptomator.CryptoVaultInterface; 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 CryptoVaultInterface vault; - public CryptoWriteFeature(final Session session, final Write proxy, final CryptoVault vault) { + public CryptoWriteFeature(final Session session, final Write proxy, final CryptoVaultInterface vault) { this.session = session; this.proxy = proxy; this.vault = vault; From f6550ea83e7c24fbf4c4f73086af8f3d8cf2e579 Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Mon, 20 Jan 2025 11:14:38 +0100 Subject: [PATCH 06/39] More extraction. --- .../core/cryptomator/AbstractVault.java | 218 ++++++++++++++++++ .../core/cryptomator/CryptoAclPermission.java | 5 +- .../cryptomator/CryptoTransferStatus.java | 4 +- .../core/cryptomator/CryptoVault.java | 183 +++------------ .../cryptomator/CryptoVaultInterface.java | 59 ----- .../cyberduck/core/cryptomator/UVFVault.java | 93 +++++++- .../features/CryptoBulkFeature.java | 6 +- .../features/CryptoChecksumCompute.java | 6 +- .../features/CryptoCopyFeature.java | 6 +- .../features/CryptoDeleteV6Feature.java | 6 +- .../features/CryptoDeleteV7Feature.java | 6 +- .../features/CryptoDirectoryV6Feature.java | 6 +- .../features/CryptoDirectoryV7Feature.java | 6 +- .../features/CryptoDownloadFeature.java | 6 +- .../features/CryptoMoveV6Feature.java | 6 +- .../features/CryptoMoveV7Feature.java | 6 +- .../features/CryptoMultipartWriteFeature.java | 6 +- .../features/CryptoReadFeature.java | 6 +- .../features/CryptoTimestampFeature.java | 6 +- .../features/CryptoTouchFeature.java | 6 +- .../features/CryptoUploadFeature.java | 6 +- .../features/CryptoWriteFeature.java | 6 +- 22 files changed, 379 insertions(+), 279 deletions(-) create mode 100644 cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java delete mode 100644 cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVaultInterface.java 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..2b3ad755e36 --- /dev/null +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java @@ -0,0 +1,218 @@ +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.Session; +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.cryptomator.cryptolib.api.Cryptor; +import org.cryptomator.cryptolib.api.FileContentCryptor; +import org.cryptomator.cryptolib.api.FileHeaderCryptor; + +public abstract class AbstractVault implements Vault { + + public static final int VAULT_VERSION_DEPRECATED = 6; + public static final int VAULT_VERSION = PreferencesFactory.get().getInteger("cryptomator.vault.version"); + + public abstract Path getMasterkey(); + + public abstract Path getConfig(); + + 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 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 abstract Path encrypt(Session session, Path file, String directoryId, boolean metadata) throws BackgroundException; + + public synchronized boolean isUnlocked() { + return this.getCryptor() != null; + } + + @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 34da1e17f5b..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 CryptoVaultInterface vault; + private final AbstractVault vault; - public CryptoTransferStatus(final CryptoVaultInterface 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 155574a72bb..43024637a79 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,20 @@ * 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.Permission; +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 +38,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; @@ -81,11 +93,9 @@ // - 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 implements CryptoVaultInterface { +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"; @@ -385,10 +395,6 @@ private PerpetualMasterkey getMasterKey(final MasterkeyFile mkFile, final CharSe 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; @@ -548,29 +554,6 @@ public long toCiphertextSize(final long cleartextFileOffset, final long cleartex return headerSize + cryptor.fileContentCryptor().ciphertextSize(cleartextFileSize); } - @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)) { @@ -594,6 +577,11 @@ public Path getConfig() { return config; } + @Override + public int getVersion() { + return vaultVersion; + } + @Override public FileHeaderCryptor getFileHeaderCryptor() { return cryptor.fileHeaderCryptor(); @@ -620,132 +608,13 @@ public CryptoDirectory getDirectoryProvider() { } @Override - public int getNonceSize() { - return nonceSize; - } - - @Override - public int numberOfChunks(final long cleartextFileSize) { - return (int) (cleartextFileSize / cryptor.fileContentCryptor().cleartextChunkSize() + - ((cleartextFileSize % cryptor.fileContentCryptor().cleartextChunkSize() > 0) ? 1 : 0)); + 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/CryptoVaultInterface.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVaultInterface.java deleted file mode 100644 index 83116b1fae6..00000000000 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVaultInterface.java +++ /dev/null @@ -1,59 +0,0 @@ -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.Path; -import ch.cyberduck.core.Session; -import ch.cyberduck.core.exception.BackgroundException; -import ch.cyberduck.core.features.Vault; - -import org.cryptomator.cryptolib.api.FileContentCryptor; -import org.cryptomator.cryptolib.api.FileHeaderCryptor; - -public interface CryptoVaultInterface extends Vault { - - Path getMasterkey(); - - Path getConfig(); - - FileHeaderCryptor getFileHeaderCryptor(); - - FileContentCryptor getFileContentCryptor(); - - CryptorCache getFileNameCryptor(); - - CryptoFilename getFilenameProvider(); - - CryptoDirectory getDirectoryProvider(); - - int getNonceSize(); - - int numberOfChunks(long cleartextFileSize); - - long toCleartextSize(final long cleartextFileOffset, final long ciphertextFileSize) throws CryptoInvalidFilesizeException; - - @Override - default Path encrypt(Session session, Path file) throws BackgroundException { - return this.encrypt(session, file, file.attributes().getDirectoryId(), false); - } - - @Override - default Path encrypt(Session session, Path file, boolean metadata) throws BackgroundException { - return this.encrypt(session, file, file.attributes().getDirectoryId(), metadata); - } - - Path encrypt(Session session, Path file, String directoryId, boolean metadata) throws BackgroundException; -} diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java index 6f7803728d6..06441ceaffe 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -19,22 +19,25 @@ 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.features.Vault; 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 implements Vault { +public class UVFVault extends AbstractVault { private static final Logger log = LogManager.getLogger(UVFVault.class); @@ -50,6 +53,8 @@ public class UVFVault implements Vault { private CryptoFilename filenameProvider; private CryptoDirectory directoryProvider; + private int nonceSize; + public UVFVault(final Path home, final String decryptedPayload) { this.home = home; this.decrypted = decryptedPayload; @@ -80,7 +85,7 @@ public UVFVault load(final Session session, final PasswordCallback prompt) th 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); - //TODO where? this.nonceSize = vaultConfig.getNonceSize(); + this.nonceSize = 12; return this; } @@ -95,28 +100,70 @@ public boolean contains(final Path file) { } @Override - public Path encrypt(final Session session, final Path file) throws BackgroundException { + public Path encrypt(final Session session, final Path file, final String directoryId, final boolean metadata) throws BackgroundException { return null; } @Override - public Path encrypt(final Session session, final Path file, final boolean metadata) throws BackgroundException { + public Path decrypt(final Session session, final Path file) throws BackgroundException { return null; } @Override - public Path decrypt(final Session session, final Path file) throws BackgroundException { + public long toCiphertextSize(final long cleartextFileOffset, final long cleartextFileSize) { + return 0; + } + + @Override + public Path getMasterkey() { + //TODO: implement return null; } @Override - public long toCiphertextSize(final long cleartextFileOffset, final long cleartextFileSize) { - return 0; + public Path getConfig() { + //TODO: implement + return null; } @Override - public long toCleartextSize(final long cleartextFileOffset, final long ciphertextFileSize) throws BackgroundException { - return 0; + 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 @@ -133,4 +180,30 @@ public State getState() { public Path getHome() { return null; } + + @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 8c866c954d6..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.CryptoVaultInterface; +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 CryptoVaultInterface cryptomator; + private final AbstractVault cryptomator; - public CryptoBulkFeature(final Session session, final Bulk delegate, final Delete delete, final CryptoVaultInterface 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 00c98ee147b..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.CryptoVaultInterface; import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator; import ch.cyberduck.core.cryptomator.random.RotatingNonceGenerator; import ch.cyberduck.core.exception.BackgroundException; @@ -55,10 +55,10 @@ public class CryptoChecksumCompute extends AbstractChecksumCompute { private static final Logger log = LogManager.getLogger(CryptoChecksumCompute.class); - private final CryptoVaultInterface cryptomator; + private final AbstractVault cryptomator; private final ChecksumCompute delegate; - public CryptoChecksumCompute(final ChecksumCompute delegate, final CryptoVaultInterface CryptoVaultInterface) { + 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 801f5a878d1..f9276c2b702 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.CryptoVaultInterface; +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; @@ -37,11 +37,11 @@ public class CryptoCopyFeature implements Copy { private final Session session; private final Copy proxy; - private final CryptoVaultInterface vault; + private final AbstractVault vault; private Session target; - public CryptoCopyFeature(final Session session, final Copy proxy, final CryptoVaultInterface 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 aa0ab7a695c..6f3f1deb849 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.CryptoVaultInterface; 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 CryptoVaultInterface vault; + private final AbstractVault vault; private final CryptoFilename filenameProvider; - public CryptoDeleteV6Feature(final Session session, final Delete proxy, final CryptoVaultInterface 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 7617eb8f143..7321c7b65c8 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 @@ -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.CryptoVaultInterface; import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryV7Provider; import ch.cyberduck.core.exception.AccessDeniedException; import ch.cyberduck.core.exception.BackgroundException; @@ -45,10 +45,10 @@ public class CryptoDeleteV7Feature implements Delete, Trash { private final Session session; private final Delete proxy; - private final CryptoVaultInterface vault; + private final AbstractVault vault; private final CryptoFilename filenameProvider; - public CryptoDeleteV7Feature(final Session session, final Delete proxy, final CryptoVaultInterface 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 1e63d0a5cab..d560749a419 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.CryptoVaultInterface; 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 CryptoVaultInterface vault; + private final AbstractVault vault; private final RandomStringService random = new UUIDRandomStringService(); public CryptoDirectoryV6Feature(final Session session, final Directory delegate, - final Write writer, final CryptoVaultInterface 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 85c9df2e62f..b5b35dd3be9 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.CryptoVaultInterface; 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 CryptoVaultInterface vault; + private final AbstractVault vault; private final RandomStringService random = new UUIDRandomStringService(); public CryptoDirectoryV7Feature(final Session session, final Directory delegate, - final Write writer, final CryptoVaultInterface 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 20386818dba..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,7 +19,7 @@ import ch.cyberduck.core.Local; import ch.cyberduck.core.Path; import ch.cyberduck.core.Session; -import ch.cyberduck.core.cryptomator.CryptoVaultInterface; +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.NotfoundException; import ch.cyberduck.core.features.Download; @@ -32,9 +32,9 @@ public class CryptoDownloadFeature implements Download { private final Session session; private final Download proxy; - private final CryptoVaultInterface vault; + private final AbstractVault vault; - public CryptoDownloadFeature(final Session session, final Download proxy, final Read reader, final CryptoVaultInterface 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/CryptoMoveV6Feature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV6Feature.java index 0a54e29ad8b..e6d80abba7c 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.CryptoVaultInterface; +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.InvalidFilenameException; import ch.cyberduck.core.features.Delete; @@ -34,9 +34,9 @@ public class CryptoMoveV6Feature implements Move { private final Session session; private final Move proxy; - private final CryptoVaultInterface vault; + private final AbstractVault vault; - public CryptoMoveV6Feature(final Session session, final Move delegate, final CryptoVaultInterface 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 225a086ef6b..5a10ad7eac5 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.CryptoVaultInterface; +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; @@ -35,9 +35,9 @@ public class CryptoMoveV7Feature implements Move { private final Session session; private final Move proxy; - private final CryptoVaultInterface vault; + private final AbstractVault vault; - public CryptoMoveV7Feature(final Session session, final Move delegate, final CryptoVaultInterface 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 f3f9f14ea2c..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.CryptoVaultInterface; +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 CryptoVaultInterface 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 CryptoVaultInterface 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 ea62bbd07af..27d84106f53 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.CryptoVaultInterface; 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 CryptoVaultInterface vault; + private final AbstractVault vault; - public CryptoReadFeature(final Session session, final Read proxy, final CryptoVaultInterface 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 70506559f95..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.CryptoVaultInterface; 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 CryptoVaultInterface vault; + private final AbstractVault vault; - public CryptoTimestampFeature(final Session session, final Timestamp proxy, final CryptoVaultInterface 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 0695127c993..fafef01dc35 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.CryptoVaultInterface; +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 CryptoVaultInterface vault; + private final AbstractVault vault; - public CryptoTouchFeature(final Session session, final Touch proxy, final Write writer, final CryptoVaultInterface 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 7beecee0570..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.CryptoVaultInterface; 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 CryptoVaultInterface vault; + private final AbstractVault vault; - public CryptoUploadFeature(final Session session, final Upload delegate, final Write writer, final CryptoVaultInterface 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 9013f7a86eb..7142146e0e1 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.CryptoVaultInterface; 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 CryptoVaultInterface vault; + private final AbstractVault vault; - public CryptoWriteFeature(final Session session, final Write proxy, final CryptoVaultInterface vault) { + public CryptoWriteFeature(final Session session, final Write proxy, final AbstractVault vault) { this.session = session; this.proxy = proxy; this.vault = vault; From b93c1b4726e1ec6e7062cc5d747e8fd6f5332bd3 Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Mon, 20 Jan 2025 15:22:37 +0100 Subject: [PATCH 07/39] More extraction. --- .../core/cryptomator/AbstractVault.java | 204 +++++++++++++++++- .../core/cryptomator/CryptoVault.java | 196 +---------------- .../cyberduck/core/cryptomator/UVFVault.java | 45 +--- 3 files changed, 220 insertions(+), 225 deletions(-) diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java index 2b3ad755e36..7ee00625a0c 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java @@ -17,7 +17,10 @@ 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; @@ -26,19 +29,38 @@ 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(); @@ -82,6 +104,26 @@ public long toCleartextSize(final long cleartextFileOffset, final long ciphertex } } + @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); @@ -92,12 +134,172 @@ public Path encrypt(Session session, Path file, boolean metadata) throws Back return this.encrypt(session, file, file.attributes().getDirectoryId(), metadata); } - public abstract Path encrypt(Session session, Path file, String directoryId, boolean metadata) throws BackgroundException; + 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) { 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 43024637a79..4efa77bdd32 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java @@ -25,7 +25,6 @@ import ch.cyberduck.core.PasswordStoreFactory; 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.UUIDRandomStringService; @@ -50,7 +49,6 @@ 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; @@ -71,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; @@ -81,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; @@ -98,11 +93,6 @@ public class CryptoVault extends AbstractVault { 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"; @@ -326,18 +316,7 @@ 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; } @@ -395,174 +374,6 @@ private PerpetualMasterkey getMasterKey(final MasterkeyFile mkFile, final CharSe new ByteArrayInputStream(writer.getBuffer().toString().getBytes(StandardCharsets.UTF_8)), passphrase); } - @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; - } - - // UVF: extract to delegate? - 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; - } - - @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(); - } - - @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); - } - - 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; } @@ -577,6 +388,11 @@ public Path getConfig() { return config; } + @Override + public Path gethHome() { + return home; + } + @Override public int getVersion() { return vaultVersion; diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java index 06441ceaffe..44de24b237e 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -71,7 +71,7 @@ public UVFVault(final Path home, final String decryptedPayload) { @Override public Path create(final Session session, final String region, final VaultCredentials credentials) throws BackgroundException { - return null; + throw new UnsupportedOperationException(); } // load -> unlock -> open @@ -90,28 +90,10 @@ public UVFVault load(final Session session, final PasswordCallback prompt) th } @Override - public void close() { - - } - - @Override - public boolean contains(final Path file) { - return false; - } - - @Override - public Path encrypt(final Session session, final Path file, final String directoryId, final boolean metadata) throws BackgroundException { - return null; - } - - @Override - public Path decrypt(final Session session, final Path file) throws BackgroundException { - return null; - } - - @Override - public long toCiphertextSize(final long cleartextFileOffset, final long cleartextFileSize) { - return 0; + public synchronized void close() { + super.close(); + cryptor = null; + fileNameCryptor = null; } @Override @@ -126,6 +108,11 @@ public Path getConfig() { return null; } + @Override + public Path gethHome() { + return home; + } + @Override public FileHeaderCryptor getFileHeaderCryptor() { return cryptor.fileHeaderCryptor(); @@ -166,19 +153,9 @@ public int getVersion() { return VAULT_VERSION; } - @Override - public T getFeature(final Session session, final Class type, final T delegate) { - return null; - } - - @Override - public State getState() { - return null; - } - @Override public Path getHome() { - return null; + return home; } @Override From e5af13a3e4a18194ea3179a8efa0b4ef543847a3 Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Thu, 6 Feb 2025 14:02:22 +0100 Subject: [PATCH 08/39] Review. --- .../src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java index 44de24b237e..e706ebf9e90 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -55,7 +55,7 @@ public class UVFVault extends AbstractVault { private int nonceSize; - public UVFVault(final Path home, final String decryptedPayload) { + public UVFVault(final Path home, final String decryptedPayload, final String config, final byte[] pepper) { this.home = home; this.decrypted = decryptedPayload; // New vault home with vault flag set for internal use @@ -83,7 +83,7 @@ public UVFVault load(final Session session, final PasswordCallback prompt) th 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.filenameProvider = new CryptoFilenameV7Provider(Integer.MAX_VALUE); // TODO there is no shortening in UVF defined yet this.directoryProvider = new CryptoDirectoryV7Provider(vault, filenameProvider, fileNameCryptor); this.nonceSize = 12; return this; From 1330c341d1a510552b4e4566faba505d772ce77e Mon Sep 17 00:00:00 2001 From: chenkins Date: Thu, 6 Feb 2025 17:17:52 +0100 Subject: [PATCH 09/39] Fix UnsupportedOperationException for fileNameCryptor() without revision. --- .../src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java index e706ebf9e90..8f7cdc93624 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -82,7 +82,7 @@ public UVFVault load(final Session session, final PasswordCallback prompt) th 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.fileNameCryptor = new CryptorCache(cryptor.fileNameCryptor(masterKey.firstRevision())); // TODO revision eventually depends on location - safe? this.filenameProvider = new CryptoFilenameV7Provider(Integer.MAX_VALUE); // TODO there is no shortening in UVF defined yet this.directoryProvider = new CryptoDirectoryV7Provider(vault, filenameProvider, fileNameCryptor); this.nonceSize = 12; From 1eb0db69618226188b7dfc4a8ef229b6b77ae91f Mon Sep 17 00:00:00 2001 From: chenkins Date: Sat, 8 Feb 2025 13:48:19 +0100 Subject: [PATCH 10/39] Bump version to 9.1.3.uvfdraft-SNAPSHOT. --- azure/pom.xml | 2 +- backblaze/pom.xml | 2 +- binding/pom.xml | 2 +- bonjour/dll/pom.xml | 2 +- bonjour/native/pom.xml | 2 +- bonjour/pom.xml | 2 +- box/pom.xml | 2 +- brick/pom.xml | 2 +- cli/dll/pom.xml | 2 +- cli/linux/pom.xml | 2 +- cli/osx/pom.xml | 2 +- cli/pom.xml | 2 +- cli/windows/pom.xml | 2 +- core/dll/pom.xml | 2 +- core/dylib/pom.xml | 2 +- core/native/pom.xml | 2 +- core/native/refresh/pom.xml | 2 +- core/pom.xml | 2 +- cryptomator/dll/pom.xml | 2 +- cryptomator/pom.xml | 2 +- ctera/pom.xml | 2 +- deepbox/pom.xml | 2 +- defaults/pom.xml | 2 +- dracoon/pom.xml | 2 +- dropbox/pom.xml | 2 +- eue/pom.xml | 2 +- freenet/pom.xml | 2 +- ftp/pom.xml | 2 +- googledrive/pom.xml | 2 +- googlestorage/pom.xml | 2 +- hubic/pom.xml | 2 +- i18n/pom.xml | 2 +- importer/dll/pom.xml | 2 +- importer/pom.xml | 2 +- irods/pom.xml | 2 +- jersey/pom.xml | 2 +- manta/pom.xml | 2 +- nextcloud/pom.xml | 2 +- nio/pom.xml | 2 +- oauth/pom.xml | 2 +- onedrive/pom.xml | 2 +- openstack/pom.xml | 2 +- osx/pom.xml | 2 +- owncloud/pom.xml | 2 +- pom.xml | 2 +- profiles/pom.xml | 2 +- protocols/dll/pom.xml | 2 +- protocols/pom.xml | 2 +- s3/pom.xml | 2 +- smb/pom.xml | 2 +- spectra/pom.xml | 2 +- ssh/pom.xml | 2 +- storegate/pom.xml | 2 +- test/pom.xml | 2 +- tus/pom.xml | 2 +- webdav/pom.xml | 2 +- windows/pom.xml | 2 +- 57 files changed, 57 insertions(+), 57 deletions(-) diff --git a/azure/pom.xml b/azure/pom.xml index 05bf0335c00..abcc4487d47 100644 --- a/azure/pom.xml +++ b/azure/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT azure diff --git a/backblaze/pom.xml b/backblaze/pom.xml index 60913e04f18..f394300555b 100644 --- a/backblaze/pom.xml +++ b/backblaze/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT backblaze jar diff --git a/binding/pom.xml b/binding/pom.xml index c512d6dc0f7..b4fbbcfe611 100644 --- a/binding/pom.xml +++ b/binding/pom.xml @@ -19,7 +19,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT binding jar diff --git a/bonjour/dll/pom.xml b/bonjour/dll/pom.xml index 125939ec011..900bb962ae3 100644 --- a/bonjour/dll/pom.xml +++ b/bonjour/dll/pom.xml @@ -5,7 +5,7 @@ ch.cyberduck parent ../../pom.xml - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT Cyberduck.Bonjour pom diff --git a/bonjour/native/pom.xml b/bonjour/native/pom.xml index 14d952b3ce9..3e552fb5f6b 100644 --- a/bonjour/native/pom.xml +++ b/bonjour/native/pom.xml @@ -5,7 +5,7 @@ ch.cyberduck parent ../../pom.xml - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT Cyberduck.Bonjour.Native pom diff --git a/bonjour/pom.xml b/bonjour/pom.xml index 948a4ad272a..3aaeb862396 100644 --- a/bonjour/pom.xml +++ b/bonjour/pom.xml @@ -18,7 +18,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT 4.0.0 diff --git a/box/pom.xml b/box/pom.xml index f4a2e48acd9..c9f8834eb12 100644 --- a/box/pom.xml +++ b/box/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT box diff --git a/brick/pom.xml b/brick/pom.xml index 7450f3f5b9f..b53c5c2e1ae 100644 --- a/brick/pom.xml +++ b/brick/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT brick jar diff --git a/cli/dll/pom.xml b/cli/dll/pom.xml index aae3208a140..34f9ddd9202 100644 --- a/cli/dll/pom.xml +++ b/cli/dll/pom.xml @@ -20,7 +20,7 @@ ch.cyberduck parent ../../pom.xml - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT Cyberduck.Cli pom diff --git a/cli/linux/pom.xml b/cli/linux/pom.xml index 494b39c0a57..935cd9ab572 100644 --- a/cli/linux/pom.xml +++ b/cli/linux/pom.xml @@ -20,7 +20,7 @@ ch.cyberduck parent ../../pom.xml - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT cli-linux Cyberduck CLI Linux diff --git a/cli/osx/pom.xml b/cli/osx/pom.xml index fe4aa09eea9..37983296958 100644 --- a/cli/osx/pom.xml +++ b/cli/osx/pom.xml @@ -20,7 +20,7 @@ ch.cyberduck parent ../../pom.xml - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT cli-osx Cyberduck CLI Mac diff --git a/cli/pom.xml b/cli/pom.xml index 002c8163e0c..ef0a1e4bac9 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -19,7 +19,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT cli diff --git a/cli/windows/pom.xml b/cli/windows/pom.xml index 59550580c54..c964e88d18e 100644 --- a/cli/windows/pom.xml +++ b/cli/windows/pom.xml @@ -20,7 +20,7 @@ ch.cyberduck parent ../../pom.xml - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT cli-windows Cyberduck CLI Windows diff --git a/core/dll/pom.xml b/core/dll/pom.xml index f0d2e563603..b123d18f24a 100644 --- a/core/dll/pom.xml +++ b/core/dll/pom.xml @@ -5,7 +5,7 @@ ch.cyberduck parent ../../pom.xml - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT Cyberduck.Core pom diff --git a/core/dylib/pom.xml b/core/dylib/pom.xml index 98821a4589f..77701573cea 100644 --- a/core/dylib/pom.xml +++ b/core/dylib/pom.xml @@ -5,7 +5,7 @@ ch.cyberduck parent ../../pom.xml - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT libcore diff --git a/core/native/pom.xml b/core/native/pom.xml index 35449ff8a7b..5ef67bbbc5c 100644 --- a/core/native/pom.xml +++ b/core/native/pom.xml @@ -5,7 +5,7 @@ ch.cyberduck parent ../../pom.xml - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT Cyberduck.Core.Native pom diff --git a/core/native/refresh/pom.xml b/core/native/refresh/pom.xml index 1a9401dabee..ee129e7bd97 100644 --- a/core/native/refresh/pom.xml +++ b/core/native/refresh/pom.xml @@ -5,7 +5,7 @@ ch.cyberduck Cyberduck.Core.Native ../pom.xml - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT Cyberduck.Core.Refresh pom diff --git a/core/pom.xml b/core/pom.xml index 6f891e500e1..e38097a5de7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT core jar diff --git a/cryptomator/dll/pom.xml b/cryptomator/dll/pom.xml index e789d1b5e3f..37e27551d2d 100644 --- a/cryptomator/dll/pom.xml +++ b/cryptomator/dll/pom.xml @@ -20,7 +20,7 @@ ch.cyberduck parent ../../pom.xml - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT Cyberduck.Cryptomator pom diff --git a/cryptomator/pom.xml b/cryptomator/pom.xml index 0b5a3fe4a80..ff13895b095 100644 --- a/cryptomator/pom.xml +++ b/cryptomator/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT cryptomator jar diff --git a/ctera/pom.xml b/ctera/pom.xml index e5a6ab2f541..b748ef0d652 100644 --- a/ctera/pom.xml +++ b/ctera/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT ctera jar diff --git a/deepbox/pom.xml b/deepbox/pom.xml index e369ff2157a..a023c70bc83 100644 --- a/deepbox/pom.xml +++ b/deepbox/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT deepbox diff --git a/defaults/pom.xml b/defaults/pom.xml index 1d720376643..bba2d526004 100644 --- a/defaults/pom.xml +++ b/defaults/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT defaults diff --git a/dracoon/pom.xml b/dracoon/pom.xml index 4a926163534..8b59a9c294a 100644 --- a/dracoon/pom.xml +++ b/dracoon/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT dracoon diff --git a/dropbox/pom.xml b/dropbox/pom.xml index 204619ec257..dba5a6e487d 100644 --- a/dropbox/pom.xml +++ b/dropbox/pom.xml @@ -18,7 +18,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT 4.0.0 dropbox diff --git a/eue/pom.xml b/eue/pom.xml index 0d2b0de5af5..7af86755c97 100644 --- a/eue/pom.xml +++ b/eue/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT eue jar diff --git a/freenet/pom.xml b/freenet/pom.xml index 47dede28b16..1cc67515406 100644 --- a/freenet/pom.xml +++ b/freenet/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT freenet jar diff --git a/ftp/pom.xml b/ftp/pom.xml index 61648eba10c..301af31d099 100644 --- a/ftp/pom.xml +++ b/ftp/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT ftp jar diff --git a/googledrive/pom.xml b/googledrive/pom.xml index a7b55a5872a..71631c38bb0 100644 --- a/googledrive/pom.xml +++ b/googledrive/pom.xml @@ -18,7 +18,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT 4.0.0 googledrive diff --git a/googlestorage/pom.xml b/googlestorage/pom.xml index 73f4aa5cfdd..0bab1508262 100644 --- a/googlestorage/pom.xml +++ b/googlestorage/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT googlestorage diff --git a/hubic/pom.xml b/hubic/pom.xml index b86233f24c2..31886ccc631 100644 --- a/hubic/pom.xml +++ b/hubic/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT hubic jar diff --git a/i18n/pom.xml b/i18n/pom.xml index 5a9746338ae..89a43779f38 100644 --- a/i18n/pom.xml +++ b/i18n/pom.xml @@ -18,7 +18,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT 4.0.0 jar diff --git a/importer/dll/pom.xml b/importer/dll/pom.xml index dcfe952d3db..cc71cd7c948 100644 --- a/importer/dll/pom.xml +++ b/importer/dll/pom.xml @@ -5,7 +5,7 @@ ch.cyberduck parent ../../pom.xml - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT Cyberduck.Importer pom diff --git a/importer/pom.xml b/importer/pom.xml index ce4f4ecf68c..c7ed91f9a8b 100644 --- a/importer/pom.xml +++ b/importer/pom.xml @@ -18,7 +18,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT 4.0.0 diff --git a/irods/pom.xml b/irods/pom.xml index 0b465e748c4..dc9f5b27044 100644 --- a/irods/pom.xml +++ b/irods/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT irods jar diff --git a/jersey/pom.xml b/jersey/pom.xml index 06ecfec237a..e5decbbe847 100644 --- a/jersey/pom.xml +++ b/jersey/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT jersey diff --git a/manta/pom.xml b/manta/pom.xml index e642aef743d..47aa8e4aeb2 100644 --- a/manta/pom.xml +++ b/manta/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT manta diff --git a/nextcloud/pom.xml b/nextcloud/pom.xml index 67678153000..8a9eb0a1c8e 100644 --- a/nextcloud/pom.xml +++ b/nextcloud/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT nextcloud jar diff --git a/nio/pom.xml b/nio/pom.xml index c732d62f15f..ad3f251fbae 100644 --- a/nio/pom.xml +++ b/nio/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT nio jar diff --git a/oauth/pom.xml b/oauth/pom.xml index 7a1673edf22..d946b3de8c1 100644 --- a/oauth/pom.xml +++ b/oauth/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT oauth diff --git a/onedrive/pom.xml b/onedrive/pom.xml index 3379dc830bd..3334136eef3 100644 --- a/onedrive/pom.xml +++ b/onedrive/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT onedrive diff --git a/openstack/pom.xml b/openstack/pom.xml index 4647dfa94e4..8bc8394475a 100644 --- a/openstack/pom.xml +++ b/openstack/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT openstack jar diff --git a/osx/pom.xml b/osx/pom.xml index dd01834ba62..5a80921e45f 100644 --- a/osx/pom.xml +++ b/osx/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT osx jar diff --git a/owncloud/pom.xml b/owncloud/pom.xml index 056b5072ccf..8a79b524c00 100644 --- a/owncloud/pom.xml +++ b/owncloud/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT owncloud jar diff --git a/pom.xml b/pom.xml index 7b8cf014906..be945b78181 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ parent Cyberduck pom - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT defaults diff --git a/profiles/pom.xml b/profiles/pom.xml index f76ad6b0e31..60d176cb1f4 100644 --- a/profiles/pom.xml +++ b/profiles/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT profiles jar diff --git a/protocols/dll/pom.xml b/protocols/dll/pom.xml index 1f3243aedbf..0437b3bbf4b 100644 --- a/protocols/dll/pom.xml +++ b/protocols/dll/pom.xml @@ -5,7 +5,7 @@ ch.cyberduck parent ../../pom.xml - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT Cyberduck.Protocols pom diff --git a/protocols/pom.xml b/protocols/pom.xml index a72a303d7ef..65112a833c1 100644 --- a/protocols/pom.xml +++ b/protocols/pom.xml @@ -18,7 +18,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT 4.0.0 pom diff --git a/s3/pom.xml b/s3/pom.xml index da2dd9bb35e..9fb9738e7a7 100644 --- a/s3/pom.xml +++ b/s3/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT s3 jar diff --git a/smb/pom.xml b/smb/pom.xml index c190ed8cb5b..ec3d396cf59 100644 --- a/smb/pom.xml +++ b/smb/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT smb jar diff --git a/spectra/pom.xml b/spectra/pom.xml index b5ed51b2051..67c52aa609f 100644 --- a/spectra/pom.xml +++ b/spectra/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT spectra jar diff --git a/ssh/pom.xml b/ssh/pom.xml index 0964d315451..a577e3ef771 100644 --- a/ssh/pom.xml +++ b/ssh/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT ssh jar diff --git a/storegate/pom.xml b/storegate/pom.xml index a26f91e85f6..ef86af8f413 100644 --- a/storegate/pom.xml +++ b/storegate/pom.xml @@ -19,7 +19,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT storegate diff --git a/test/pom.xml b/test/pom.xml index e4a6d2dbffa..474f80dd688 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT 4.0.0 pom diff --git a/tus/pom.xml b/tus/pom.xml index 9742e3382bb..2d66e75cebb 100644 --- a/tus/pom.xml +++ b/tus/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT tus jar diff --git a/webdav/pom.xml b/webdav/pom.xml index 8afa76658f5..33daec6778c 100644 --- a/webdav/pom.xml +++ b/webdav/pom.xml @@ -18,7 +18,7 @@ ch.cyberduck parent - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT webdav jar diff --git a/windows/pom.xml b/windows/pom.xml index c380891d918..4da4e1234a8 100644 --- a/windows/pom.xml +++ b/windows/pom.xml @@ -18,7 +18,7 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT 4.0.0 Cyberduck.Native From aedf1bdb5a304bc126d4455574a20a4eb9b32ad4 Mon Sep 17 00:00:00 2001 From: chenkins Date: Tue, 11 Feb 2025 10:56:36 +0100 Subject: [PATCH 11/39] Temporarily skip SFTPCryptomatorInteroperabilityTest because of dependency to cryptofs (incompatible with cryptolib uvfdraft). --- pom.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pom.xml b/pom.xml index be945b78181..60f63d696fc 100644 --- a/pom.xml +++ b/pom.xml @@ -94,6 +94,7 @@ 8u312b07 0.9.1 ch.cyberduck.test.IntegrationTest + ch.cyberduck.core.cryptomator.SFTPCryptomatorInteroperabilityTest @@ -555,6 +556,9 @@ ${project.build.directory} ${surefire.group.excluded} + + ${surefire.exclude} + false From 4ed03810606cb04554093ac378d701fec16e02b2 Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Mon, 24 Feb 2025 14:49:20 +0100 Subject: [PATCH 12/39] Switch to byte array for directory ids to avoid lossy data conversion. --- .../ch/cyberduck/core/PathAttributes.java | 6 ++-- .../core/cryptomator/AbstractVault.java | 5 ++- .../core/cryptomator/ContentReader.java | 15 +++++++++ .../core/cryptomator/CryptoDirectory.java | 4 +-- .../core/cryptomator/CryptorCache.java | 12 ++++--- .../features/CryptoBulkFeature.java | 3 +- .../features/CryptoDirectoryV6Feature.java | 6 ++-- .../features/CryptoDirectoryV7Feature.java | 6 ++-- .../impl/CryptoDirectoryV6Provider.java | 31 ++++++++++--------- .../impl/CryptoDirectoryV7Provider.java | 11 +++---- .../core/cryptomator/CryptorCacheTest.java | 11 ++++--- .../features/CryptoBulkFeatureTest.java | 2 +- 12 files changed, 65 insertions(+), 47 deletions(-) diff --git a/core/src/main/java/ch/cyberduck/core/PathAttributes.java b/core/src/main/java/ch/cyberduck/core/PathAttributes.java index d94ed8880c6..62eba587522 100644 --- a/core/src/main/java/ch/cyberduck/core/PathAttributes.java +++ b/core/src/main/java/ch/cyberduck/core/PathAttributes.java @@ -158,7 +158,7 @@ public class PathAttributes extends Attributes implements Serializable { /** * Unique identifier for cryptomator */ - private String directoryId; + private byte[] directoryId; private Map custom = Collections.emptyMap(); @@ -526,11 +526,11 @@ public PathAttributes withLockId(final String lockId) { return this; } - public String getDirectoryId() { + public byte[] getDirectoryId() { return directoryId; } - public void setDirectoryId(final String directoryId) { + public void setDirectoryId(final byte[] directoryId) { this.directoryId = directoryId; } diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java index 7ee00625a0c..7235412e5be 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java @@ -36,7 +36,6 @@ 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; @@ -134,7 +133,7 @@ public Path encrypt(Session session, Path file, boolean metadata) throws Back return this.encrypt(session, file, file.attributes().getDirectoryId(), metadata); } - public Path encrypt(Session session, Path file, String directoryId, boolean metadata) throws BackgroundException { + public Path encrypt(Session session, Path file, byte[] directoryId, boolean metadata) throws BackgroundException { final Path encrypted; if(file.isFile() || metadata) { if(file.getType().contains(Path.Type.vault)) { @@ -212,7 +211,7 @@ public Path decrypt(final Session session, final Path file) throws Background 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)); + ciphertext, file.getParent().attributes().getDirectoryId()); final PathAttributes attributes = new PathAttributes(file.attributes()); if(this.isDirectory(inflated)) { if(Permission.EMPTY != attributes.getPermission()) { diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/ContentReader.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/ContentReader.java index 1a768dd32d0..d07d450ff43 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/ContentReader.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/ContentReader.java @@ -21,10 +21,12 @@ import ch.cyberduck.core.Session; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.Read; +import ch.cyberduck.core.io.StreamCopier; import ch.cyberduck.core.transfer.TransferStatus; import org.apache.commons.io.IOUtils; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -49,6 +51,19 @@ public String read(final Path file) throws BackgroundException { } } + public byte[] readBytes(final Path file) throws BackgroundException { + final Read read = session._getFeature(Read.class); + final TransferStatus status = new TransferStatus().withLength(file.attributes().getSize()); + try (final InputStream in = read.read(file, status, new DisabledConnectionCallback())) { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + new StreamCopier(status, status).transfer(in, out); + return out.toByteArray(); + } + catch(IOException e) { + throw new DefaultIOExceptionMappingService().map(e); + } + } + public Reader getReader(final Path file) throws BackgroundException { final Read read = session._getFeature(Read.class); return new InputStreamReader(read.read(file, new TransferStatus().withLength(file.attributes().getSize()), new DisabledConnectionCallback()), StandardCharsets.UTF_8); diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoDirectory.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoDirectory.java index 88ccb32e736..2fdfa62c617 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoDirectory.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoDirectory.java @@ -32,7 +32,7 @@ public interface CryptoDirectory { * @param type File type * @return Encrypted filename */ - String toEncrypted(Session session, String directoryId, String filename, EnumSet type) throws BackgroundException; + String toEncrypted(Session session, byte[] directoryId, String filename, EnumSet type) throws BackgroundException; /** * Get encrypted reference for clear text directory path. @@ -41,7 +41,7 @@ public interface CryptoDirectory { * @param directoryId Directory ID or null to read directory id from metadata file * @param directory Clear text */ - Path toEncrypted(Session session, String directoryId, Path directory) throws BackgroundException; + Path toEncrypted(Session session, byte[] directoryId, Path directory) throws BackgroundException; /** * Remove from cache diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptorCache.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptorCache.java index f62cf502146..96bc1893bbf 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptorCache.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptorCache.java @@ -20,6 +20,7 @@ import org.cryptomator.cryptolib.api.AuthenticationFailedException; import org.cryptomator.cryptolib.api.FileNameCryptor; +import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Objects; @@ -29,7 +30,7 @@ public class CryptorCache { public static final BaseEncoding BASE32 = BaseEncoding.base32(); - private final LRUCache directoryIdCache = LRUCache.build(250); + private final LRUCache directoryIdCache = LRUCache.build(250); private final LRUCache decryptCache = LRUCache.build(5000); private final LRUCache encryptCache = LRUCache.build(5000); @@ -39,11 +40,12 @@ public CryptorCache(final FileNameCryptor impl) { this.impl = impl; } - public String hashDirectoryId(final String cleartextDirectoryId) { - if(!directoryIdCache.contains(cleartextDirectoryId)) { - directoryIdCache.put(cleartextDirectoryId, impl.hashDirectoryId(cleartextDirectoryId)); + public String hashDirectoryId(final byte[] cleartextDirectoryId) { + final ByteBuffer wrap = ByteBuffer.wrap(cleartextDirectoryId); + if(!directoryIdCache.contains(wrap)) { + directoryIdCache.put(wrap, impl.hashDirectoryId(cleartextDirectoryId)); } - return directoryIdCache.get(cleartextDirectoryId); + return directoryIdCache.get(wrap); } public String encryptFilename(final BaseEncoding encoding, final String cleartextName, final byte[] associatedData) { 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 1d8947823e4..aa6357876b2 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 @@ -34,6 +34,7 @@ import org.cryptomator.cryptolib.api.FileHeader; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -84,7 +85,7 @@ public int compare(final Map.Entry o1, final Map.E switch(type) { case upload: // Preset directory ID for new folders to avert lookup with not found failure in directory ID provider - final String directoryId = random.random(); + final byte[] directoryId = random.random().getBytes(StandardCharsets.US_ASCII); encrypted.put(new TransferItem(cryptomator.encrypt(session, file, directoryId, false), local), status); break; default: 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 d560749a419..b9112a2e95d 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 @@ -53,12 +53,12 @@ public CryptoDirectoryV6Feature(final Session session, final Directory @Override public Path mkdir(final Path folder, final TransferStatus status) throws BackgroundException { - final Path encrypt = vault.encrypt(session, folder, random.random(), false); - final String directoryId = encrypt.attributes().getDirectoryId(); + final Path encrypt = vault.encrypt(session, folder, random.random().getBytes(StandardCharsets.US_ASCII), false); + final byte[] directoryId = encrypt.attributes().getDirectoryId(); // Create metadata file for directory final Path directoryMetadataFile = vault.encrypt(session, folder, true); log.debug("Write metadata {} for folder {}", directoryMetadataFile, folder); - new ContentWriter(session).write(directoryMetadataFile, directoryId.getBytes(StandardCharsets.UTF_8)); + new ContentWriter(session).write(directoryMetadataFile, directoryId); final Path intermediate = encrypt.getParent(); if(!session._getFeature(Find.class).find(intermediate)) { session._getFeature(Directory.class).mkdir(intermediate, new TransferStatus().withRegion(status.getRegion())); 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 b5b35dd3be9..8609bc0f161 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 @@ -55,8 +55,8 @@ public CryptoDirectoryV7Feature(final Session session, final Directory @Override public Path mkdir(final Path folder, final TransferStatus status) throws BackgroundException { - final Path encrypt = vault.encrypt(session, folder, random.random(), false); - final String directoryId = encrypt.attributes().getDirectoryId(); + final Path encrypt = vault.encrypt(session, folder, random.random().getBytes(StandardCharsets.US_ASCII), false); + final byte[] directoryId = encrypt.attributes().getDirectoryId(); // Create metadata file for directory final Path directoryMetadataFolder = session._getFeature(Directory.class).mkdir(vault.encrypt(session, folder, true), new TransferStatus().withRegion(status.getRegion())); @@ -64,7 +64,7 @@ public Path mkdir(final Path folder, final TransferStatus status) throws Backgro CryptoDirectoryV7Provider.DIRECTORY_METADATAFILE, EnumSet.of(Path.Type.file)); log.debug("Write metadata {} for folder {}", directoryMetadataFile, folder); - new ContentWriter(session).write(directoryMetadataFile, directoryId.getBytes(StandardCharsets.UTF_8)); + new ContentWriter(session).write(directoryMetadataFile, directoryId); final Path intermediate = encrypt.getParent(); if(!session._getFeature(Find.class).find(intermediate)) { session._getFeature(Directory.class).mkdir(intermediate, new TransferStatus().withRegion(status.getRegion())); 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 9ad0728a9c4..13b991b8f49 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 @@ -32,11 +32,12 @@ import ch.cyberduck.core.exception.NotfoundException; import ch.cyberduck.core.preferences.PreferencesFactory; -import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.EnumSet; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -45,7 +46,7 @@ public class CryptoDirectoryV6Provider implements CryptoDirectory { private static final Logger log = LogManager.getLogger(CryptoDirectoryV6Provider.class); private static final String DATA_DIR_NAME = "d"; - private static final String ROOT_DIR_ID = StringUtils.EMPTY; + private static final byte[] ROOT_DIR_ID = new byte[0]; private final Path dataRoot; private final Path home; @@ -57,7 +58,7 @@ public class CryptoDirectoryV6Provider implements CryptoDirectory { private final Lock lock = new ReentrantLock(); - private final LRUCache, String> cache = LRUCache.build( + private final LRUCache, byte[]> cache = LRUCache.build( PreferencesFactory.get().getInteger("cryptomator.cache.size")); public CryptoDirectoryV6Provider(final Path vault, final CryptoFilename filenameProvider, final CryptorCache filenameCryptor) { @@ -68,15 +69,15 @@ public CryptoDirectoryV6Provider(final Path vault, final CryptoFilename filename } @Override - public String toEncrypted(final Session session, final String directoryId, final String filename, final EnumSet type) throws BackgroundException { + public String toEncrypted(final Session session, final byte[] directoryId, final String filename, final EnumSet type) throws BackgroundException { final String prefix = type.contains(Path.Type.directory) ? CryptoVault.DIR_PREFIX : ""; - final String ciphertextName = prefix + filenameCryptor.encryptFilename(CryptorCache.BASE32, filename, directoryId.getBytes(StandardCharsets.UTF_8)); + final String ciphertextName = prefix + filenameCryptor.encryptFilename(CryptorCache.BASE32, filename, directoryId); log.debug("Encrypted filename {} to {}", filename, ciphertextName); return filenameProvider.deflate(session, ciphertextName); } @Override - public Path toEncrypted(final Session session, final String directoryId, final Path directory) throws BackgroundException { + public Path toEncrypted(final Session session, final byte[] directoryId, final Path directory) throws BackgroundException { if(!directory.isDirectory()) { throw new NotfoundException(directory.getAbsolute()); } @@ -86,7 +87,7 @@ public Path toEncrypted(final Session session, final String directoryId, fina attributes.withVersionId(null); attributes.withFileId(null); // Remember random directory id for use in vault - final String id = this.toDirectoryId(session, directory, directoryId); + final byte[] id = this.toDirectoryId(session, directory, directoryId); log.debug("Use directory ID '{}' for folder {}", id, directory); attributes.setDirectoryId(id); attributes.setDecrypted(directory); @@ -102,18 +103,18 @@ public Path toEncrypted(final Session session, final String directoryId, fina throw new NotfoundException(directory.getAbsolute()); } - private String toDirectoryId(final Session session, final Path directory, final String directoryId) throws BackgroundException { + private byte[] toDirectoryId(final Session session, final Path directory, final byte[] directoryId) throws BackgroundException { if(new SimplePathPredicate(home).test(directory)) { return ROOT_DIR_ID; } - if(StringUtils.isBlank(directoryId)) { + if(ArrayUtils.isEmpty(directoryId)) { if(cache.contains(new SimplePathPredicate(directory))) { return cache.get(new SimplePathPredicate(directory)); } try { log.debug("Acquire lock for {}", directory); lock.lock(); - final String id = this.load(session, directory); + final byte[] id = this.load(session, directory); cache.put(new SimplePathPredicate(directory), id); return id; } @@ -125,15 +126,15 @@ private String toDirectoryId(final Session session, final Path directory, fin cache.put(new SimplePathPredicate(directory), directoryId); } else { - final String existing = cache.get(new SimplePathPredicate(directory)); - if(!existing.equals(directoryId)) { + final byte[] existing = cache.get(new SimplePathPredicate(directory)); + if(!Arrays.equals(existing, directoryId)) { log.warn("Do not override already cached id {} with {}", existing, directoryId); } } return cache.get(new SimplePathPredicate(directory)); } - protected String load(final Session session, final Path directory) throws BackgroundException { + protected byte[] load(final Session session, final Path directory) throws BackgroundException { final Path parent = this.toEncrypted(session, directory.getParent().attributes().getDirectoryId(), directory.getParent()); final String cleartextName = directory.getName(); final String ciphertextName = this.toEncrypted(session, parent.attributes().getDirectoryId(), cleartextName, EnumSet.of(Path.Type.directory)); @@ -141,11 +142,11 @@ protected String load(final Session session, final Path directory) throws Bac try { log.debug("Read directory ID for folder {} from {}", directory, ciphertextName); final Path metadataFile = new Path(parent, ciphertextName, EnumSet.of(Path.Type.file, Path.Type.encrypted)); - return new ContentReader(session).read(metadataFile); + return new ContentReader(session).readBytes(metadataFile); } catch(NotfoundException e) { log.warn("Missing directory ID for folder {}", directory); - return random.random(); + return random.random().getBytes(StandardCharsets.US_ASCII); } } 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 cfb9a8b55e2..484a33b7657 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 @@ -55,14 +55,13 @@ public CryptoDirectoryV7Provider(final Path vault, final CryptoFilename filename } @Override - public String toEncrypted(final Session session, final String directoryId, final String filename, final EnumSet type) throws BackgroundException { - final String ciphertextName = filenameCryptor.encryptFilename(BaseEncoding.base64Url(), - filename, directoryId.getBytes(StandardCharsets.UTF_8)) + EXTENSION_REGULAR; + public String toEncrypted(final Session session, final byte[] directoryId, final String filename, final EnumSet type) throws BackgroundException { + final String ciphertextName = filenameCryptor.encryptFilename(BaseEncoding.base64Url(), filename, directoryId) + EXTENSION_REGULAR; log.debug("Encrypted filename {} to {}", filename, ciphertextName); return filenameProvider.deflate(session, ciphertextName); } - protected String load(final Session session, final Path directory) throws BackgroundException { + protected byte[] load(final Session session, final Path directory) throws BackgroundException { final Path parent = this.toEncrypted(session, directory.getParent().attributes().getDirectoryId(), directory.getParent()); final String cleartextName = directory.getName(); final String ciphertextName = this.toEncrypted(session, parent.attributes().getDirectoryId(), cleartextName, EnumSet.of(Path.Type.directory)); @@ -71,11 +70,11 @@ protected String load(final Session session, final Path directory) throws Bac try { log.debug("Read directory ID for folder {} from {}", directory, ciphertextName); final Path metadataFile = new Path(metadataParent, CryptoDirectoryV7Provider.DIRECTORY_METADATAFILE, EnumSet.of(Path.Type.file, Path.Type.encrypted)); - return new ContentReader(session).read(metadataFile); + return new ContentReader(session).readBytes(metadataFile); } catch(NotfoundException e) { log.warn("Missing directory ID for folder {}", directory); - return random.random(); + return random.random().getBytes(StandardCharsets.US_ASCII); } } } diff --git a/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/CryptorCacheTest.java b/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/CryptorCacheTest.java index f88038fc29e..94deb11d3d5 100644 --- a/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/CryptorCacheTest.java +++ b/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/CryptorCacheTest.java @@ -19,10 +19,11 @@ import org.cryptomator.cryptolib.api.FileNameCryptor; import org.junit.Test; +import java.nio.charset.StandardCharsets; + import com.google.common.io.BaseEncoding; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; public class CryptorCacheTest { @@ -31,10 +32,10 @@ public class CryptorCacheTest { public void TestHashDirectoryId() { final FileNameCryptor mock = mock(FileNameCryptor.class); final CryptorCache cryptor = new CryptorCache(mock); - when(mock.hashDirectoryId(anyString())).thenReturn("hashed"); - assertEquals("hashed", cryptor.hashDirectoryId("id")); - assertEquals("hashed", cryptor.hashDirectoryId("id")); - verify(mock, times(1)).hashDirectoryId(anyString()); + when(mock.hashDirectoryId(any(byte[].class))).thenReturn("hashed"); + assertEquals("hashed", cryptor.hashDirectoryId("id".getBytes(StandardCharsets.US_ASCII))); + assertEquals("hashed", cryptor.hashDirectoryId("id".getBytes(StandardCharsets.US_ASCII))); + verify(mock, times(1)).hashDirectoryId(any(byte[].class)); verifyNoMoreInteractions(mock); } diff --git a/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/features/CryptoBulkFeatureTest.java b/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/features/CryptoBulkFeatureTest.java index 52f12e31f97..6499bae0cb2 100644 --- a/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/features/CryptoBulkFeatureTest.java +++ b/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/features/CryptoBulkFeatureTest.java @@ -105,7 +105,7 @@ public boolean test(final TransferItem item) { return item.remote.isDirectory(); } }).findFirst().get().remote; - final String directoryId = encryptedDirectory.attributes().getDirectoryId(); + final byte[] directoryId = encryptedDirectory.attributes().getDirectoryId(); assertNotNull(directoryId); for(TransferItem file : pre.keySet().stream().filter(new Predicate() { @Override From 5baa253c29682071df2bcbcbd6d5539e9b71a36f Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Tue, 25 Feb 2025 12:18:39 +0100 Subject: [PATCH 13/39] Directory id for root is derived from key material. --- .../cyberduck/core/cryptomator/UVFVault.java | 4 +- .../impl/CryptoDirectoryUVFProvider.java | 41 +++++++++++++++++++ .../impl/CryptoDirectoryV6Provider.java | 2 +- 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java index 8f7cdc93624..d6a038ffc85 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -20,7 +20,7 @@ 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.CryptoDirectoryUVFProvider; import ch.cyberduck.core.cryptomator.impl.CryptoFilenameV7Provider; import ch.cyberduck.core.cryptomator.random.FastSecureRandomProvider; import ch.cyberduck.core.exception.BackgroundException; @@ -84,7 +84,7 @@ public UVFVault load(final Session session, final PasswordCallback prompt) th this.cryptor = provider.provide(masterKey, FastSecureRandomProvider.get().provide()); this.fileNameCryptor = new CryptorCache(cryptor.fileNameCryptor(masterKey.firstRevision())); // TODO revision eventually depends on location - safe? this.filenameProvider = new CryptoFilenameV7Provider(Integer.MAX_VALUE); // TODO there is no shortening in UVF defined yet - this.directoryProvider = new CryptoDirectoryV7Provider(vault, filenameProvider, fileNameCryptor); + this.directoryProvider = new CryptoDirectoryUVFProvider(vault, filenameProvider, fileNameCryptor); this.nonceSize = 12; return this; } diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java new file mode 100644 index 00000000000..db8900e5854 --- /dev/null +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java @@ -0,0 +1,41 @@ +package ch.cyberduck.core.cryptomator.impl; + +/* + * 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.Path; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.SimplePathPredicate; +import ch.cyberduck.core.cryptomator.CryptoFilename; +import ch.cyberduck.core.cryptomator.CryptorCache; +import ch.cyberduck.core.exception.BackgroundException; + +public class CryptoDirectoryUVFProvider extends CryptoDirectoryV7Provider { + + private final Path home; + + public CryptoDirectoryUVFProvider(final Path vault, final CryptoFilename filenameProvider, final CryptorCache filenameCryptor) { + super(vault, filenameProvider, filenameCryptor); + this.home = vault; + } + + @Override + protected byte[] toDirectoryId(final Session session, final Path directory, final byte[] directoryId) throws BackgroundException { + if(new SimplePathPredicate(home).test(directory)) { + return directoryId; + } + return super.toDirectoryId(session, directory, directoryId); + } +} 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 13b991b8f49..1d7e236452a 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 @@ -103,7 +103,7 @@ public Path toEncrypted(final Session session, final byte[] directoryId, fina throw new NotfoundException(directory.getAbsolute()); } - private byte[] toDirectoryId(final Session session, final Path directory, final byte[] directoryId) throws BackgroundException { + protected byte[] toDirectoryId(final Session session, final Path directory, final byte[] directoryId) throws BackgroundException { if(new SimplePathPredicate(home).test(directory)) { return ROOT_DIR_ID; } From 6d2ce9f4e63535a3db073867cfbb07812e27dea0 Mon Sep 17 00:00:00 2001 From: chenkins Date: Tue, 25 Feb 2025 14:09:01 +0100 Subject: [PATCH 14/39] Fix cryptolib uvfdraft version. --- cryptomator/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cryptomator/pom.xml b/cryptomator/pom.xml index ff13895b095..d78c32f369f 100644 --- a/cryptomator/pom.xml +++ b/cryptomator/pom.xml @@ -25,7 +25,7 @@ jar - 2.3.0-uvfdraft-SNAPSHOT + 2.3.0.uvfdraft-SNAPSHOT From e977086a7a85bd6dfd8be4e6e7cc1750d0e48322 Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Wed, 26 Feb 2025 15:53:16 +0100 Subject: [PATCH 15/39] Fix typo. --- .../cyberduck/core/cryptomator/AbstractVault.java | 14 ++++++-------- .../ch/cyberduck/core/cryptomator/CryptoVault.java | 5 ----- .../ch/cyberduck/core/cryptomator/UVFVault.java | 7 +------ 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java index 7235412e5be..d5728367c2e 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java @@ -58,8 +58,6 @@ public abstract class AbstractVault implements Vault { public abstract Path getConfig(); - public abstract Path gethHome(); - public abstract int getVersion(); public abstract FileHeaderCryptor getFileHeaderCryptor(); @@ -140,7 +138,7 @@ public Path encrypt(Session session, Path file, byte[] directoryId, boolean m log.warn("Skip file {} because it is marked as an internal vault path", file); return file; } - if(new SimplePathPredicate(file).test(this.gethHome())) { + if(new SimplePathPredicate(file).test(this.getHome())) { log.warn("Skip vault home {} because the root has no metadata file", file); return file; } @@ -179,7 +177,7 @@ public Path encrypt(Session session, Path file, byte[] directoryId, boolean m return file; } if(file.getType().contains(Path.Type.vault)) { - return this.getDirectoryProvider().toEncrypted(session, this.gethHome().attributes().getDirectoryId(), this.gethHome()); + return this.getDirectoryProvider().toEncrypted(session, this.getHome().attributes().getDirectoryId(), this.getHome()); } encrypted = this.getDirectoryProvider().toEncrypted(session, directoryId, file); } @@ -188,8 +186,8 @@ public Path encrypt(Session session, Path file, byte[] directoryId, boolean m encrypted.attributes().setDecrypted(file); } // Add reference for vault - file.attributes().setVault(this.gethHome()); - encrypted.attributes().setVault(this.gethHome()); + file.attributes().setVault(this.getHome()); + encrypted.attributes().setVault(this.getHome()); return encrypted; } @@ -233,7 +231,7 @@ public Path decrypt(final Session session, final Path file) throws Background // Add reference to encrypted file attributes.setEncrypted(file); // Add reference for vault - attributes.setVault(this.gethHome()); + attributes.setVault(this.getHome()); 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); @@ -279,7 +277,7 @@ public synchronized boolean isUnlocked() { @Override public boolean contains(final Path file) { if(this.isUnlocked()) { - return new SimplePathPredicate(file).test(this.gethHome()) || file.isChild(this.getHome()); + return new SimplePathPredicate(file).test(this.getHome()) || file.isChild(this.getHome()); } return false; } 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 4efa77bdd32..ac3bafaf771 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java @@ -388,11 +388,6 @@ public Path getConfig() { return config; } - @Override - public Path gethHome() { - return home; - } - @Override public int getVersion() { return vaultVersion; diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java index d6a038ffc85..8c650625ad7 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -109,7 +109,7 @@ public Path getConfig() { } @Override - public Path gethHome() { + public Path getHome() { return home; } @@ -153,11 +153,6 @@ public int getVersion() { return VAULT_VERSION; } - @Override - public Path getHome() { - return home; - } - @Override public boolean equals(final Object o) { if(this == o) { From 29adf98aa4fa9000ffaf14583f85e6523d3be95b Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Thu, 27 Feb 2025 10:34:33 +0100 Subject: [PATCH 16/39] Metadata file/directory extension is .uvf. --- .../core/cryptomator/AbstractVault.java | 6 +++++ .../core/cryptomator/CryptoVault.java | 24 +++++++++++++++++-- .../cyberduck/core/cryptomator/UVFVault.java | 23 +++++++++++++++++- .../features/CryptoDeleteV7Feature.java | 7 +++--- .../features/CryptoDirectoryV7Feature.java | 7 +++--- .../features/CryptoMoveV7Feature.java | 9 ++++--- .../impl/CryptoDirectoryUVFProvider.java | 5 ++-- .../impl/CryptoDirectoryV7Provider.java | 17 ++++++------- .../impl/CryptoDirectoryV7ProviderTest.java | 7 +++--- 9 files changed, 74 insertions(+), 31 deletions(-) diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java index d5728367c2e..a7596da526e 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java @@ -282,6 +282,12 @@ public boolean contains(final Path file) { return false; } + public abstract String getRegularFileExtension(); + + public abstract String getDirectoryMetadataFilename(); + + public abstract String getBackupDirectoryMetadataFilename(); + @Override public synchronized void close() { if(this.isUnlocked()) { 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 ac3bafaf771..5f4b622a70f 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java @@ -97,6 +97,12 @@ public class CryptoVault extends AbstractVault { private static final String JSON_KEY_CIPHERCONFIG = "cipherCombo"; private static final String JSON_KEY_SHORTENING_THRESHOLD = "shorteningThreshold"; + private static final String REGULAR_FILE_EXTENSION = ".c9r"; + private static final String FILENAME_DIRECTORYID = "dir"; + private static final String DIRECTORY_METADATA_FILENAME = String.format("%s%s", FILENAME_DIRECTORYID, REGULAR_FILE_EXTENSION); + private static final String BACKUP_FILENAME_DIRECTORYID = "dirid"; + private static final String BACKUP_DIRECTORY_METADATA_FILENAME = String.format("%s%s", BACKUP_FILENAME_DIRECTORYID, REGULAR_FILE_EXTENSION); + /** * Root of vault directory */ @@ -119,7 +125,6 @@ public class CryptoVault extends AbstractVault { 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); } @@ -336,7 +341,7 @@ protected CryptoDirectory createDirectoryProvider(final VaultConfig vaultConfig, case VAULT_VERSION_DEPRECATED: return new CryptoDirectoryV6Provider(vault, filenameProvider, filenameCryptor); default: - return new CryptoDirectoryV7Provider(vault, filenameProvider, filenameCryptor); + return new CryptoDirectoryV7Provider(this, filenameProvider, filenameCryptor); } } @@ -428,6 +433,21 @@ public int getNonceSize() { return nonceSize; } + @Override + public String getRegularFileExtension() { + return REGULAR_FILE_EXTENSION; + } + + @Override + public String getDirectoryMetadataFilename() { + return DIRECTORY_METADATA_FILENAME; + } + + @Override + public String getBackupDirectoryMetadataFilename() { + return BACKUP_DIRECTORY_METADATA_FILENAME; + } + @Override public boolean equals(final Object o) { if(this == o) { diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java index 8c650625ad7..a912493accd 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -41,6 +41,12 @@ public class UVFVault extends AbstractVault { private static final Logger log = LogManager.getLogger(UVFVault.class); + private static final String REGULAR_FILE_EXTENSION = ".uvf"; + private static final String FILENAME_DIRECTORYID = "dir"; + private static final String DIRECTORY_METADATA_FILENAME = String.format("%s%s", FILENAME_DIRECTORYID, REGULAR_FILE_EXTENSION); + private static final String BACKUP_FILENAME_DIRECTORYID = "dirid"; + private static final String BACKUP_DIRECTORY_METADATA_FILENAME = String.format("%s%s", BACKUP_FILENAME_DIRECTORYID, REGULAR_FILE_EXTENSION); + /** * Root of vault directory */ @@ -84,7 +90,7 @@ public UVFVault load(final Session session, final PasswordCallback prompt) th this.cryptor = provider.provide(masterKey, FastSecureRandomProvider.get().provide()); this.fileNameCryptor = new CryptorCache(cryptor.fileNameCryptor(masterKey.firstRevision())); // TODO revision eventually depends on location - safe? this.filenameProvider = new CryptoFilenameV7Provider(Integer.MAX_VALUE); // TODO there is no shortening in UVF defined yet - this.directoryProvider = new CryptoDirectoryUVFProvider(vault, filenameProvider, fileNameCryptor); + this.directoryProvider = new CryptoDirectoryUVFProvider(this, filenameProvider, fileNameCryptor); this.nonceSize = 12; return this; } @@ -153,6 +159,21 @@ public int getVersion() { return VAULT_VERSION; } + @Override + public String getRegularFileExtension() { + return REGULAR_FILE_EXTENSION; + } + + @Override + public String getDirectoryMetadataFilename() { + return DIRECTORY_METADATA_FILENAME; + } + + @Override + public String getBackupDirectoryMetadataFilename() { + return BACKUP_DIRECTORY_METADATA_FILENAME; + } + @Override public boolean equals(final Object o) { if(this == o) { 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 7321c7b65c8..a4d7fe66a2b 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 @@ -22,7 +22,6 @@ import ch.cyberduck.core.Session; import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.cryptomator.CryptoFilename; -import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryV7Provider; import ch.cyberduck.core.exception.AccessDeniedException; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.NotfoundException; @@ -62,8 +61,8 @@ public void delete(final Map files, final PasswordCallback if(!f.equals(vault.getHome())) { final Path encrypt = vault.encrypt(session, f); if(f.isDirectory()) { - final Path backup = new Path(encrypt, CryptoDirectoryV7Provider.BACKUP_DIRECTORY_METADATAFILE, - EnumSet.of(Path.Type.file)); + final Path backup = new Path(encrypt, vault.getBackupDirectoryMetadataFilename(), + EnumSet.of(AbstractPath.Type.file)); try { log.debug("Deleting directory id backup file {}", backup); proxy.delete(Collections.singletonList(backup), prompt, callback); @@ -87,7 +86,7 @@ public void delete(final Map files, final PasswordCallback } final Path metadata = vault.encrypt(session, f, true); if(f.isDirectory()) { - final Path metadataFile = new Path(metadata, CryptoDirectoryV7Provider.DIRECTORY_METADATAFILE, EnumSet.of(Path.Type.file)); + final Path metadataFile = new Path(metadata, vault.getDirectoryMetadataFilename(), EnumSet.of(Path.Type.file)); log.debug("Add metadata file {}", metadataFile); metadataFiles.add(metadataFile); metadataFiles.add(metadata); 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 8609bc0f161..b48598107f6 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 @@ -21,7 +21,6 @@ import ch.cyberduck.core.UUIDRandomStringService; import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.cryptomator.ContentWriter; -import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryV7Provider; import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.Directory; @@ -46,11 +45,11 @@ public class CryptoDirectoryV7Feature implements Directory { private final RandomStringService random = new UUIDRandomStringService(); public CryptoDirectoryV7Feature(final Session session, final Directory delegate, - final Write writer, final AbstractVault cryptomator) { + final Write writer, final AbstractVault vault) { this.session = session; this.writer = writer; this.delegate = delegate; - this.vault = cryptomator; + this.vault = vault; } @Override @@ -61,7 +60,7 @@ public Path mkdir(final Path folder, final TransferStatus status) throws Backgro final Path directoryMetadataFolder = session._getFeature(Directory.class).mkdir(vault.encrypt(session, folder, true), new TransferStatus().withRegion(status.getRegion())); final Path directoryMetadataFile = new Path(directoryMetadataFolder, - CryptoDirectoryV7Provider.DIRECTORY_METADATAFILE, + vault.getDirectoryMetadataFilename(), EnumSet.of(Path.Type.file)); log.debug("Write metadata {} for folder {}", directoryMetadataFile, folder); new ContentWriter(session).write(directoryMetadataFile, directoryId); 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 5a10ad7eac5..ea1c2fd5659 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 @@ -20,7 +20,6 @@ import ch.cyberduck.core.Path; import ch.cyberduck.core.Session; 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; import ch.cyberduck.core.features.Delete; @@ -37,10 +36,10 @@ public class CryptoMoveV7Feature implements Move { private final Move proxy; private final AbstractVault vault; - public CryptoMoveV7Feature(final Session session, final Move delegate, final AbstractVault cryptomator) { + public CryptoMoveV7Feature(final Session session, final Move delegate, final AbstractVault vault) { this.session = session; this.proxy = delegate; - this.vault = cryptomator; + this.vault = vault; } @Override @@ -51,8 +50,8 @@ public Path move(final Path file, final Path renamed, final TransferStatus statu final Path target = proxy.move(sourceEncrypted, targetEncrypted, status, callback, connectionCallback); if(file.isDirectory()) { if(!proxy.isRecursive(file, renamed)) { - proxy.move(new Path(sourceEncrypted, CryptoDirectoryV7Provider.DIRECTORY_METADATAFILE, EnumSet.of(Path.Type.file)), - new Path(targetEncrypted, CryptoDirectoryV7Provider.DIRECTORY_METADATAFILE, EnumSet.of(Path.Type.file)), + proxy.move(new Path(sourceEncrypted, vault.getDirectoryMetadataFilename(), EnumSet.of(Path.Type.file)), + new Path(targetEncrypted, vault.getBackupDirectoryMetadataFilename(), EnumSet.of(Path.Type.file)), new TransferStatus(status), callback, connectionCallback); } vault.getDirectoryProvider().delete(file); diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java index db8900e5854..8f7c28fc93b 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java @@ -18,6 +18,7 @@ import ch.cyberduck.core.Path; import ch.cyberduck.core.Session; import ch.cyberduck.core.SimplePathPredicate; +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.cryptomator.CryptoFilename; import ch.cyberduck.core.cryptomator.CryptorCache; import ch.cyberduck.core.exception.BackgroundException; @@ -26,9 +27,9 @@ public class CryptoDirectoryUVFProvider extends CryptoDirectoryV7Provider { private final Path home; - public CryptoDirectoryUVFProvider(final Path vault, final CryptoFilename filenameProvider, final CryptorCache filenameCryptor) { + public CryptoDirectoryUVFProvider(final AbstractVault vault, final CryptoFilename filenameProvider, final CryptorCache filenameCryptor) { super(vault, filenameProvider, filenameCryptor); - this.home = vault; + this.home = vault.getHome(); } @Override 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 484a33b7657..97ca6161b5f 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 @@ -19,6 +19,7 @@ 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.ContentReader; import ch.cyberduck.core.cryptomator.CryptoFilename; import ch.cyberduck.core.cryptomator.CryptorCache; @@ -36,27 +37,23 @@ public class CryptoDirectoryV7Provider extends CryptoDirectoryV6Provider { private static final Logger log = LogManager.getLogger(CryptoDirectoryV7Provider.class); - public static final String EXTENSION_REGULAR = ".c9r"; - public static final String FILENAME_DIRECTORYID = "dir"; - public static final String DIRECTORY_METADATAFILE = String.format("%s%s", FILENAME_DIRECTORYID, EXTENSION_REGULAR); - 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 CryptoFilename filenameProvider; private final CryptorCache filenameCryptor; + private final AbstractVault vault; private final RandomStringService random = new UUIDRandomStringService(); - public CryptoDirectoryV7Provider(final Path vault, final CryptoFilename filenameProvider, final CryptorCache filenameCryptor) { - super(vault, filenameProvider, filenameCryptor); + public CryptoDirectoryV7Provider(final AbstractVault vault, final CryptoFilename filenameProvider, final CryptorCache filenameCryptor) { + super(vault.getHome(), filenameProvider, filenameCryptor); this.filenameProvider = filenameProvider; this.filenameCryptor = filenameCryptor; + this.vault = vault; } @Override public String toEncrypted(final Session session, final byte[] directoryId, final String filename, final EnumSet type) throws BackgroundException { - final String ciphertextName = filenameCryptor.encryptFilename(BaseEncoding.base64Url(), filename, directoryId) + EXTENSION_REGULAR; + final String ciphertextName = filenameCryptor.encryptFilename(BaseEncoding.base64Url(), filename, directoryId) + vault.getRegularFileExtension(); log.debug("Encrypted filename {} to {}", filename, ciphertextName); return filenameProvider.deflate(session, ciphertextName); } @@ -69,7 +66,7 @@ protected byte[] load(final Session session, final Path directory) throws Bac // Read directory id from file try { log.debug("Read directory ID for folder {} from {}", directory, ciphertextName); - final Path metadataFile = new Path(metadataParent, CryptoDirectoryV7Provider.DIRECTORY_METADATAFILE, EnumSet.of(Path.Type.file, Path.Type.encrypted)); + final Path metadataFile = new Path(metadataParent, vault.getDirectoryMetadataFilename(), EnumSet.of(Path.Type.file, Path.Type.encrypted)); return new ContentReader(session).readBytes(metadataFile); } catch(NotfoundException e) { 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 7ad14d49335..dedc79f25e0 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 @@ -20,6 +20,7 @@ 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.cryptomator.CryptorCache; import ch.cyberduck.core.exception.NotfoundException; @@ -42,7 +43,7 @@ public void testToEncryptedInvalidArgument() throws Exception { 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())); + final CryptoDirectory provider = new CryptoDirectoryV7Provider(new CryptoVault(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))); } @@ -52,7 +53,7 @@ public void testToEncryptedInvalidPath() throws Exception { 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())); + final CryptoDirectory provider = new CryptoDirectoryV7Provider(new CryptoVault(home), new CryptoFilenameV7Provider(), new CryptorCache(cryptor.fileNameCryptor())); provider.toEncrypted(new NullSession(new Host(new TestProtocol())), null, new Path("/", EnumSet.of(Path.Type.directory))); } @@ -63,7 +64,7 @@ public void testToEncryptedDirectory() throws Exception { 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())); + final CryptoDirectory provider = new CryptoDirectoryV7Provider(new CryptoVault(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)); From ac3512d2f975db079adfc72d84528910c1be321c Mon Sep 17 00:00:00 2001 From: chenkins Date: Thu, 27 Feb 2025 10:59:33 +0100 Subject: [PATCH 17/39] Fix compile. --- .../core/cryptomator/features/CryptoDeleteV7Feature.java | 1 + 1 file changed, 1 insertion(+) 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 a4d7fe66a2b..b4ffb1e212c 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 @@ -15,6 +15,7 @@ * GNU General Public License for more details. */ +import ch.cyberduck.core.AbstractPath; import ch.cyberduck.core.DisabledListProgressListener; import ch.cyberduck.core.ListService; import ch.cyberduck.core.PasswordCallback; From ad5c7844795e6632456f5e1e50db47017ded1b87 Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Thu, 27 Feb 2025 14:02:57 +0100 Subject: [PATCH 18/39] Fix base64 URL pattern. --- .../java/ch/cyberduck/core/cryptomator/AbstractVault.java | 5 +++-- .../java/ch/cyberduck/core/cryptomator/CryptoVault.java | 8 ++++++++ .../main/java/ch/cyberduck/core/cryptomator/UVFVault.java | 8 ++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java index a7596da526e..cb0b492d290 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java @@ -52,7 +52,6 @@ public abstract class AbstractVault implements Vault { 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(); @@ -202,7 +201,7 @@ public Path decrypt(final Session session, final Path file) throws Background return file; } final Path inflated = this.inflate(session, file); - final Pattern pattern = this.getVersion() == VAULT_VERSION_DEPRECATED ? BASE32_PATTERN : BASE64URL_PATTERN; + final Pattern pattern = this.getVersion() == VAULT_VERSION_DEPRECATED ? BASE32_PATTERN : this.getBase64URLPattern(); final Matcher m = pattern.matcher(inflated.getName()); if(m.matches()) { final String ciphertext = m.group(1); @@ -288,6 +287,8 @@ public boolean contains(final Path file) { public abstract String getBackupDirectoryMetadataFilename(); + public abstract Pattern getBase64URLPattern(); + @Override public synchronized void close() { if(this.isUnlocked()) { 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 5f4b622a70f..755fc2a407a 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java @@ -69,6 +69,7 @@ import java.text.MessageFormat; import java.util.EnumSet; import java.util.Objects; +import java.util.regex.Pattern; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; @@ -103,6 +104,8 @@ public class CryptoVault extends AbstractVault { private static final String BACKUP_FILENAME_DIRECTORYID = "dirid"; private static final String BACKUP_DIRECTORY_METADATA_FILENAME = String.format("%s%s", BACKUP_FILENAME_DIRECTORYID, REGULAR_FILE_EXTENSION); + private static final Pattern BASE64URL_PATTERN = Pattern.compile("^([A-Za-z0-9_=-]+)." + REGULAR_FILE_EXTENSION); + /** * Root of vault directory */ @@ -448,6 +451,11 @@ public String getBackupDirectoryMetadataFilename() { return BACKUP_DIRECTORY_METADATA_FILENAME; } + @Override + public Pattern getBase64URLPattern() { + return BASE64URL_PATTERN; + } + @Override public boolean equals(final Object o) { if(this == o) { diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java index a912493accd..db6e421db3a 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -36,6 +36,7 @@ import java.util.EnumSet; import java.util.Objects; +import java.util.regex.Pattern; public class UVFVault extends AbstractVault { @@ -47,6 +48,8 @@ public class UVFVault extends AbstractVault { private static final String BACKUP_FILENAME_DIRECTORYID = "dirid"; private static final String BACKUP_DIRECTORY_METADATA_FILENAME = String.format("%s%s", BACKUP_FILENAME_DIRECTORYID, REGULAR_FILE_EXTENSION); + private static final Pattern BASE64URL_PATTERN = Pattern.compile("^([A-Za-z0-9_=-]+)." + REGULAR_FILE_EXTENSION); + /** * Root of vault directory */ @@ -174,6 +177,11 @@ public String getBackupDirectoryMetadataFilename() { return BACKUP_DIRECTORY_METADATA_FILENAME; } + @Override + public Pattern getBase64URLPattern() { + return BASE64URL_PATTERN; + } + @Override public boolean equals(final Object o) { if(this == o) { From 8f8437de4bf349640c0dd23d998f72a0f79ddda4 Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Thu, 27 Feb 2025 14:25:11 +0100 Subject: [PATCH 19/39] Fix pattern. --- .../main/java/ch/cyberduck/core/cryptomator/CryptoVault.java | 2 +- .../src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 755fc2a407a..661161fc2c3 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java @@ -104,7 +104,7 @@ public class CryptoVault extends AbstractVault { private static final String BACKUP_FILENAME_DIRECTORYID = "dirid"; private static final String BACKUP_DIRECTORY_METADATA_FILENAME = String.format("%s%s", BACKUP_FILENAME_DIRECTORYID, REGULAR_FILE_EXTENSION); - private static final Pattern BASE64URL_PATTERN = Pattern.compile("^([A-Za-z0-9_=-]+)." + REGULAR_FILE_EXTENSION); + private static final Pattern BASE64URL_PATTERN = Pattern.compile("^([A-Za-z0-9_=-]+)" + REGULAR_FILE_EXTENSION); /** * Root of vault directory diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java index db6e421db3a..cebaf4cbb7a 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -48,7 +48,7 @@ public class UVFVault extends AbstractVault { private static final String BACKUP_FILENAME_DIRECTORYID = "dirid"; private static final String BACKUP_DIRECTORY_METADATA_FILENAME = String.format("%s%s", BACKUP_FILENAME_DIRECTORYID, REGULAR_FILE_EXTENSION); - private static final Pattern BASE64URL_PATTERN = Pattern.compile("^([A-Za-z0-9_=-]+)." + REGULAR_FILE_EXTENSION); + private static final Pattern BASE64URL_PATTERN = Pattern.compile("^([A-Za-z0-9_=-]+)" + REGULAR_FILE_EXTENSION); /** * Root of vault directory From 13f3fa92c9431262987db825adf47aa2a7c7046a Mon Sep 17 00:00:00 2001 From: David Kocher Date: Thu, 6 Mar 2025 08:48:20 +0100 Subject: [PATCH 20/39] Delete unused. --- .../java/ch/cyberduck/core/cryptomator/UVFVault.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java index cebaf4cbb7a..5fb72c23cc0 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -17,7 +17,6 @@ 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.CryptoDirectoryUVFProvider; @@ -34,7 +33,6 @@ import org.cryptomator.cryptolib.api.FileHeaderCryptor; import org.cryptomator.cryptolib.api.UVFMasterkey; -import java.util.EnumSet; import java.util.Objects; import java.util.regex.Pattern; @@ -54,7 +52,6 @@ public class UVFVault extends AbstractVault { * Root of vault directory */ private final Path home; - private final Path vault; private final String decrypted; private Cryptor cryptor; @@ -67,15 +64,6 @@ public class UVFVault extends AbstractVault { public UVFVault(final Path home, final String decryptedPayload, final String config, final byte[] pepper) { 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 From 04eeba6e9a9bb45bc7f8ca6442b6a8028a7abf3b Mon Sep 17 00:00:00 2001 From: David Kocher Date: Thu, 6 Mar 2025 09:59:08 +0100 Subject: [PATCH 21/39] Require to pass UVF metadata payload in password callback. --- .../cyberduck/core/cryptomator/UVFVault.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java index 5fb72c23cc0..82986ec0011 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -15,6 +15,8 @@ * GNU General Public License for more details. */ +import ch.cyberduck.core.LocaleFactory; +import ch.cyberduck.core.LoginOptions; import ch.cyberduck.core.PasswordCallback; import ch.cyberduck.core.Path; import ch.cyberduck.core.Session; @@ -33,6 +35,7 @@ import org.cryptomator.cryptolib.api.FileHeaderCryptor; import org.cryptomator.cryptolib.api.UVFMasterkey; +import java.text.MessageFormat; import java.util.Objects; import java.util.regex.Pattern; @@ -53,7 +56,6 @@ public class UVFVault extends AbstractVault { */ private final Path home; - private final String decrypted; private Cryptor cryptor; private CryptorCache fileNameCryptor; private CryptoFilename filenameProvider; @@ -61,9 +63,8 @@ public class UVFVault extends AbstractVault { private int nonceSize; - public UVFVault(final Path home, final String decryptedPayload, final String config, final byte[] pepper) { + public UVFVault(final Path home) { this.home = home; - this.decrypted = decryptedPayload; } @Override @@ -74,8 +75,15 @@ public Path create(final Session session, final String region, final VaultCre // load -> unlock -> open @Override public UVFVault load(final Session session, final PasswordCallback prompt) throws BackgroundException { - UVFMasterkey masterKey = UVFMasterkey.fromDecryptedPayload(this.decrypted); - + final UVFMasterkey masterKey = UVFMasterkey.fromDecryptedPayload(prompt.prompt(session.getHost(), + LocaleFactory.localizedString("Unlock Vault", "Cryptomator"), + MessageFormat.format(LocaleFactory.localizedString("Provide your passphrase to unlock the Cryptomator Vault {0}", "Cryptomator"), home.getName()), + new LoginOptions() + .save(false) + .user(false) + .anonymous(false) + .icon("cryptomator.tiff") + .passwordPlaceholder(LocaleFactory.localizedString("Passphrase", "Cryptomator"))).getPassword()); final CryptorProvider provider = CryptorProvider.forScheme(CryptorProvider.Scheme.UVF_DRAFT); log.debug("Initialized crypto provider {}", provider); this.cryptor = provider.provide(masterKey, FastSecureRandomProvider.get().provide()); From 278165b20c713388ceab20e6a572e4e4ec7ac37e Mon Sep 17 00:00:00 2001 From: David Kocher Date: Thu, 6 Mar 2025 10:10:22 +0100 Subject: [PATCH 22/39] Destroy on close. --- .../src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java index 82986ec0011..5a20913abd2 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -97,8 +97,7 @@ public UVFVault load(final Session session, final PasswordCallback prompt) th @Override public synchronized void close() { super.close(); - cryptor = null; - fileNameCryptor = null; + cryptor.destroy(); } @Override From 354f2eb7a858004e9301b6d40bccd9640ad46ad3 Mon Sep 17 00:00:00 2001 From: chenkins Date: Fri, 7 Mar 2025 22:31:14 +0100 Subject: [PATCH 23/39] Add UVF test listing files from vault created with cryptolib. --- .../cyberduck/core/cryptomator/UVFTest.java | 160 ++++++++++++++++++ .../GsMMTRvsuuP_6NjgRwopmWcuof-PyRQ=.uvf | Bin 0 -> 68 bytes .../RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/dir.uvf | Bin 0 -> 128 bytes .../dir.uvf | Bin 0 -> 128 bytes .../0rAlpJBOfOCNkoummiK96xSJvAFLPbk=.uvf | Bin 0 -> 68 bytes .../WV/ZUTJPJT6FR7ZQRRW4FD2DPPBKJOIIF/dir.uvf | Bin 0 -> 128 bytes 6 files changed, 160 insertions(+) create mode 100644 s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java create mode 100644 s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/GsMMTRvsuuP_6NjgRwopmWcuof-PyRQ=.uvf create mode 100644 s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/dir.uvf create mode 100644 s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/rExOms183v5evFwgIKiW0qvbsor1Hg==.uvf/dir.uvf create mode 100644 s3/src/test/resources/uvf/first_vault/d/WV/ZUTJPJT6FR7ZQRRW4FD2DPPBKJOIIF/0rAlpJBOfOCNkoummiK96xSJvAFLPbk=.uvf create mode 100644 s3/src/test/resources/uvf/first_vault/d/WV/ZUTJPJT6FR7ZQRRW4FD2DPPBKJOIIF/dir.uvf diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java new file mode 100644 index 00000000000..019b3cae868 --- /dev/null +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java @@ -0,0 +1,160 @@ +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.AbstractPath; +import ch.cyberduck.core.Credentials; +import ch.cyberduck.core.DisabledCancelCallback; +import ch.cyberduck.core.DisabledConnectionCallback; +import ch.cyberduck.core.DisabledHostKeyCallback; +import ch.cyberduck.core.DisabledListProgressListener; +import ch.cyberduck.core.DisabledLoginCallback; +import ch.cyberduck.core.DisabledPasswordCallback; +import ch.cyberduck.core.Host; +import ch.cyberduck.core.ListService; +import ch.cyberduck.core.LoginOptions; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.PathAttributes; +import ch.cyberduck.core.Scheme; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.AttributesFinder; +import ch.cyberduck.core.features.Bulk; +import ch.cyberduck.core.features.Write; +import ch.cyberduck.core.io.StatusOutputStream; +import ch.cyberduck.core.proxy.ProxyFactory; +import ch.cyberduck.core.s3.S3Protocol; +import ch.cyberduck.core.s3.S3Session; +import ch.cyberduck.core.shared.DefaultPathHomeFeature; +import ch.cyberduck.core.sts.AbstractAssumeRoleWithWebIdentityTest; +import ch.cyberduck.core.transfer.Transfer; +import ch.cyberduck.core.transfer.TransferItem; +import ch.cyberduck.core.transfer.TransferStatus; +import ch.cyberduck.core.vault.DefaultVaultRegistry; +import ch.cyberduck.core.vault.VaultRegistry; + +import org.apache.commons.io.IOUtils; +import org.cryptomator.cryptolib.api.UVFMasterkey; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.testcontainers.containers.ComposeContainer; +import org.testcontainers.containers.wait.strategy.Wait; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +public class UVFTest { + + private static final ComposeContainer container = new ComposeContainer( + new File(AbstractAssumeRoleWithWebIdentityTest.class.getResource("/testcontainer/docker-compose.yml").getFile())) + .withPull(false) +// .withLocalCompose(true) + .withExposedService("minio-1", 9000, Wait.forListeningPort()); + + @BeforeClass + public static void start() { + container.start(); + } + + @AfterClass + public static void shutdown() { + container.stop(); + } + + @Test + public void listMinio() throws BackgroundException, IOException { + final Host bookmark = new Host(new S3Protocol() { + @Override + public Scheme getScheme() { + return Scheme.http; + } + }, "minio", 9000).withCredentials(new Credentials("cyberduckAccessKey", "cyberduckSecretKey")); + bookmark.setProperty("s3.bucket.virtualhost.disable", "true"); + bookmark.setDefaultPath("/cyberduckbucket"); + final S3Session storage = new S3Session(bookmark); + + storage.open(ProxyFactory.get(), new DisabledHostKeyCallback(), new DisabledLoginCallback(), new DisabledCancelCallback()); + storage.login(new DisabledLoginCallback() { + @Override + public Credentials prompt(final Host bookmark, final String username, final String title, final String reason, + final LoginOptions options) { + return storage.getHost().getCredentials(); + } + }, + new DisabledCancelCallback()); + final List files = Arrays.asList( + "/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/rExOms183v5evFwgIKiW0qvbsor1Hg==.uvf/dir.uvf", + "/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/dir.uvf", + "/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/GsMMTRvsuuP_6NjgRwopmWcuof-PyRQ=.uvf", + "/d/WV/ZUTJPJT6FR7ZQRRW4FD2DPPBKJOIIF/0rAlpJBOfOCNkoummiK96xSJvAFLPbk=.uvf", + "/d/WV/ZUTJPJT6FR7ZQRRW4FD2DPPBKJOIIF/dir.uvf" + ); + final String jwe = "{\n" + + " \"fileFormat\": \"AES-256-GCM-32k\",\n" + + " \"nameFormat\": \"AES-SIV-512-B64URL\",\n" + + " \"seeds\": {\n" + + " \"HDm38g\": \"ypeBEsobvcr6wjGzmiPcTaeG7/gUfE5yuYB3ha/uSLs=\",\n" + + " \"gBryKw\": \"PiPoFgA5WUoziU9lZOGxNIu9egCI1CxKy3PurtWcAJ0=\",\n" + + " \"QBsJFg\": \"Ln0sA6lQeuJl7PW1NWiFpTOTogKdJBOUmXJloaJa78Y=\"\n" + + " },\n" + + " \"initialSeed\": \"HDm38i\",\n" + + " \"latestSeed\": \"QBsJFo\",\n" + + " \"kdf\": \"HKDF-SHA512\",\n" + + " \"kdfSalt\": \"NIlr89R7FhochyP4yuXZmDqCnQ0dBB3UZ2D+6oiIjr8=\"\n" + + "}"; + + for(final String fi : files) { + final Path file = new Path("/cyberduckbucket/" + fi, EnumSet.of(AbstractPath.Type.file)); + byte[] content = new byte[1000]; + final int size = UVFTest.class.getResourceAsStream("/uvf/first_vault" + fi).read(content); + final TransferStatus transferStatus = new TransferStatus().withLength(size); + transferStatus.setChecksum(storage.getFeature(Write.class).checksum(file, transferStatus).compute(new ByteArrayInputStream(content), transferStatus)); + storage.getFeature(Bulk.class).pre(Transfer.Type.upload, Collections.singletonMap(new TransferItem(file), transferStatus), new DisabledConnectionCallback()); + final StatusOutputStream out = storage.getFeature(Write.class).write(file, transferStatus, new DisabledConnectionCallback()); + IOUtils.copyLarge(UVFTest.class.getResourceAsStream("/uvf/first_vault" + fi), out); + out.close(); + } + + final VaultRegistry vaults = new DefaultVaultRegistry(new DisabledPasswordCallback()); + + final UVFVault vault = new UVFVault(new DefaultPathHomeFeature(bookmark).find()); + + vaults.add(vault.load(storage, new DisabledPasswordCallback() { + @Override + public Credentials prompt(final Host bookmark, final String title, final String reason, final LoginOptions options) { + return new Credentials().withPassword(jwe); + } + })); + final PathAttributes attr = storage.getFeature(AttributesFinder.class).find(vault.getHome()); + try( + UVFMasterkey masterKey = UVFMasterkey.fromDecryptedPayload(jwe)) { + attr.setDirectoryId(masterKey.rootDirId()); + } + storage.withRegistry(vaults); + // TODO should be fixed with https://github.com/iterate-ch/cryptolib/pull/12, see https://github.com/cryptomator/cryptolib/pull/51/commits/1e9bd327d6a04f9eb617fe83564eae3ec65a48f8 + final BackgroundException backgroundException = assertThrows(BackgroundException.class, () -> storage.getFeature(ListService.class).list(vault.getHome().withAttributes(attr).withType(EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.vault)), new DisabledListProgressListener())); + assertEquals("File not found", backgroundException.getMessage()); + assertEquals("/cyberduckbucket/d/RK/HZLENL3PQIW6GZHE3KRRRGLFBHWHRU. Please contact your web hosting service provider for assistance.", backgroundException.getDetail()); + } +} diff --git a/s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/GsMMTRvsuuP_6NjgRwopmWcuof-PyRQ=.uvf b/s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/GsMMTRvsuuP_6NjgRwopmWcuof-PyRQ=.uvf new file mode 100644 index 0000000000000000000000000000000000000000..88a963c92e1bd545ef4d3ebdc421722ced9a50c3 GIT binary patch literal 68 zcmV-K0K5Nnc4h!T8wnPvZAqy0Pyjq535p)HewK*nxO*Qo){e9DaTz=vWsODjuJweg a+QMp%v?Bil%~Bts1uuV_-jVicIo{9aQXm}w literal 0 HcmV?d00001 diff --git a/s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/dir.uvf b/s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/dir.uvf new file mode 100644 index 0000000000000000000000000000000000000000..29d546f2565c43d62b9f440132a781df2c730025 GIT binary patch literal 128 zcmV-`0Du2=c4h!T8wnQ3OA?nmC~KG6udMu)v|O~Xj~jOHAsz3neBP7-@>2m^el}Ts z*su30lDFz6!=DjJQY`RVvtS7LM{Ljd=kV2@4f+FwHI)|R_Rzm^+^!oi#4C6JJm-3` iz<$?TxYHNQCMC_q0~^dSL>oy|)@v1)a?e)ypCojfEk51= literal 0 HcmV?d00001 diff --git a/s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/rExOms183v5evFwgIKiW0qvbsor1Hg==.uvf/dir.uvf b/s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/rExOms183v5evFwgIKiW0qvbsor1Hg==.uvf/dir.uvf new file mode 100644 index 0000000000000000000000000000000000000000..2dfd958d8986f9648ea95b2cf45a4dd7bf5c27cf GIT binary patch literal 128 zcmV-`0Du2=c4h!T8wnPO-_*8tLXr8x5O{-wTx2a}%2wPdPQH!zw$RH%>}S|2=rPud zH9C2qIRb3r3P}h#heyKNHWS(5;xMzXx(6XAZ({^L>7jf;+kY5l%^zI=w+U0F7cDc4Kdo)+du?V} zg(&STm@s(Qzw-Uw=zVCO^&8tmI#$}o2;@_sX;RF({2>YY&z6if;DB)NU);cx#&K`2 iyTtfO%*9(tonCS)`Vh;>$Ycksv8jpCLzz{UJR`V=wn3Nx literal 0 HcmV?d00001 From dba0ec7377196f83d5a94aed3654be1aa5285d62 Mon Sep 17 00:00:00 2001 From: chenkins Date: Fri, 7 Mar 2025 22:56:22 +0100 Subject: [PATCH 24/39] Add category TestcontainerTest. --- s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java index 019b3cae868..e52102acabc 100644 --- a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java @@ -44,12 +44,14 @@ import ch.cyberduck.core.transfer.TransferStatus; import ch.cyberduck.core.vault.DefaultVaultRegistry; import ch.cyberduck.core.vault.VaultRegistry; +import ch.cyberduck.test.TestcontainerTest; import org.apache.commons.io.IOUtils; import org.cryptomator.cryptolib.api.UVFMasterkey; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.testcontainers.containers.ComposeContainer; import org.testcontainers.containers.wait.strategy.Wait; @@ -64,6 +66,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; +@Category(TestcontainerTest.class) public class UVFTest { private static final ComposeContainer container = new ComposeContainer( From 9907923bb2a7e4d849e1613df4260b0114b6deb9 Mon Sep 17 00:00:00 2001 From: chenkins Date: Fri, 7 Mar 2025 22:59:03 +0100 Subject: [PATCH 25/39] Fix hostname. --- s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java index e52102acabc..915b0705a81 100644 --- a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java @@ -92,7 +92,7 @@ public void listMinio() throws BackgroundException, IOException { public Scheme getScheme() { return Scheme.http; } - }, "minio", 9000).withCredentials(new Credentials("cyberduckAccessKey", "cyberduckSecretKey")); + }, "localhost", 9000).withCredentials(new Credentials("cyberduckAccessKey", "cyberduckSecretKey")); bookmark.setProperty("s3.bucket.virtualhost.disable", "true"); bookmark.setDefaultPath("/cyberduckbucket"); final S3Session storage = new S3Session(bookmark); From a8650481dfbc9068a9c8c2febde769689c6de9df Mon Sep 17 00:00:00 2001 From: chenkins Date: Sat, 8 Mar 2025 14:07:42 +0100 Subject: [PATCH 26/39] Simplify setup for uvf integration test. --- .../cyberduck/core/cryptomator/UVFTest.java | 143 ++++++++++-------- s3/src/test/resources/uvf/docker-compose.yml | 44 ++++++ 2 files changed, 127 insertions(+), 60 deletions(-) create mode 100644 s3/src/test/resources/uvf/docker-compose.yml diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java index 915b0705a81..3ad3cbf1850 100644 --- a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java @@ -15,26 +15,15 @@ * GNU General Public License for more details. */ -import ch.cyberduck.core.AbstractPath; -import ch.cyberduck.core.Credentials; -import ch.cyberduck.core.DisabledCancelCallback; -import ch.cyberduck.core.DisabledConnectionCallback; -import ch.cyberduck.core.DisabledHostKeyCallback; -import ch.cyberduck.core.DisabledListProgressListener; -import ch.cyberduck.core.DisabledLoginCallback; -import ch.cyberduck.core.DisabledPasswordCallback; -import ch.cyberduck.core.Host; -import ch.cyberduck.core.ListService; -import ch.cyberduck.core.LoginOptions; -import ch.cyberduck.core.Path; -import ch.cyberduck.core.PathAttributes; -import ch.cyberduck.core.Scheme; +import ch.cyberduck.core.*; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.AttributesFinder; import ch.cyberduck.core.features.Bulk; +import ch.cyberduck.core.features.Delete; import ch.cyberduck.core.features.Write; import ch.cyberduck.core.io.StatusOutputStream; import ch.cyberduck.core.proxy.ProxyFactory; +import ch.cyberduck.core.s3.S3BucketCreateService; import ch.cyberduck.core.s3.S3Protocol; import ch.cyberduck.core.s3.S3Session; import ch.cyberduck.core.shared.DefaultPathHomeFeature; @@ -44,10 +33,12 @@ import ch.cyberduck.core.transfer.TransferStatus; import ch.cyberduck.core.vault.DefaultVaultRegistry; import ch.cyberduck.core.vault.VaultRegistry; +import ch.cyberduck.core.worker.DeleteWorker; import ch.cyberduck.test.TestcontainerTest; import org.apache.commons.io.IOUtils; import org.cryptomator.cryptolib.api.UVFMasterkey; +import org.jetbrains.annotations.NotNull; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -58,10 +49,13 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.util.AbstractMap; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; @@ -70,9 +64,14 @@ public class UVFTest { private static final ComposeContainer container = new ComposeContainer( - new File(AbstractAssumeRoleWithWebIdentityTest.class.getResource("/testcontainer/docker-compose.yml").getFile())) + new File(AbstractAssumeRoleWithWebIdentityTest.class.getResource("/uvf/docker-compose.yml").getFile())) .withPull(false) // .withLocalCompose(true) + .withEnv( + Stream.of( + new AbstractMap.SimpleImmutableEntry<>("MINIO_PORT", "9000"), + new AbstractMap.SimpleImmutableEntry<>("MINIO_CONSOLE_PORT", "9001") + ).collect(Collectors.toMap(AbstractMap.SimpleImmutableEntry::getKey, AbstractMap.SimpleImmutableEntry::getValue))) .withExposedService("minio-1", 9000, Wait.forListeningPort()); @BeforeClass @@ -87,25 +86,14 @@ public static void shutdown() { @Test public void listMinio() throws BackgroundException, IOException { - final Host bookmark = new Host(new S3Protocol() { - @Override - public Scheme getScheme() { - return Scheme.http; - } - }, "localhost", 9000).withCredentials(new Credentials("cyberduckAccessKey", "cyberduckSecretKey")); - bookmark.setProperty("s3.bucket.virtualhost.disable", "true"); - bookmark.setDefaultPath("/cyberduckbucket"); - final S3Session storage = new S3Session(bookmark); + final String bucketName = "cyberduckbucket"; + + final Host bookmark = getMinIOBookmark(); + final S3Session storage = getS3SessionForBookmark(bookmark); + + final Path bucket = new Path(bucketName, EnumSet.of(AbstractPath.Type.directory)); + new S3BucketCreateService(storage).create(bucket, "us-east-1"); - storage.open(ProxyFactory.get(), new DisabledHostKeyCallback(), new DisabledLoginCallback(), new DisabledCancelCallback()); - storage.login(new DisabledLoginCallback() { - @Override - public Credentials prompt(final Host bookmark, final String username, final String title, final String reason, - final LoginOptions options) { - return storage.getHost().getCredentials(); - } - }, - new DisabledCancelCallback()); final List files = Arrays.asList( "/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/rExOms183v5evFwgIKiW0qvbsor1Hg==.uvf/dir.uvf", "/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/dir.uvf", @@ -127,37 +115,72 @@ public Credentials prompt(final Host bookmark, final String username, final Stri " \"kdfSalt\": \"NIlr89R7FhochyP4yuXZmDqCnQ0dBB3UZ2D+6oiIjr8=\"\n" + "}"; - for(final String fi : files) { - final Path file = new Path("/cyberduckbucket/" + fi, EnumSet.of(AbstractPath.Type.file)); - byte[] content = new byte[1000]; - final int size = UVFTest.class.getResourceAsStream("/uvf/first_vault" + fi).read(content); - final TransferStatus transferStatus = new TransferStatus().withLength(size); - transferStatus.setChecksum(storage.getFeature(Write.class).checksum(file, transferStatus).compute(new ByteArrayInputStream(content), transferStatus)); - storage.getFeature(Bulk.class).pre(Transfer.Type.upload, Collections.singletonMap(new TransferItem(file), transferStatus), new DisabledConnectionCallback()); - final StatusOutputStream out = storage.getFeature(Write.class).write(file, transferStatus, new DisabledConnectionCallback()); - IOUtils.copyLarge(UVFTest.class.getResourceAsStream("/uvf/first_vault" + fi), out); - out.close(); - } + try { + for(final String fi : files) { + final Path file = new Path("/" + bucketName + "/" + fi, EnumSet.of(AbstractPath.Type.file)); + byte[] content = new byte[1000]; + final int size = UVFTest.class.getResourceAsStream("/uvf/first_vault" + fi).read(content); + final TransferStatus transferStatus = new TransferStatus().withLength(size); + transferStatus.setChecksum(storage.getFeature(Write.class).checksum(file, transferStatus).compute(new ByteArrayInputStream(content), transferStatus)); + storage.getFeature(Bulk.class).pre(Transfer.Type.upload, Collections.singletonMap(new TransferItem(file), transferStatus), new DisabledConnectionCallback()); + final StatusOutputStream out = storage.getFeature(Write.class).write(file, transferStatus, new DisabledConnectionCallback()); + IOUtils.copyLarge(UVFTest.class.getResourceAsStream("/uvf/first_vault" + fi), out); + out.close(); + } - final VaultRegistry vaults = new DefaultVaultRegistry(new DisabledPasswordCallback()); - final UVFVault vault = new UVFVault(new DefaultPathHomeFeature(bookmark).find()); + final VaultRegistry vaults = new DefaultVaultRegistry(new DisabledPasswordCallback()); + bookmark.setDefaultPath("/" + bucketName); + final UVFVault vault = new UVFVault(new DefaultPathHomeFeature(bookmark).find()); + vaults.add(vault.load(storage, new DisabledPasswordCallback() { + @Override + public Credentials prompt(final Host bookmark, final String title, final String reason, final LoginOptions options) { + return new Credentials().withPassword(jwe); + } + })); + final PathAttributes attr = storage.getFeature(AttributesFinder.class).find(vault.getHome()); + try( + UVFMasterkey masterKey = UVFMasterkey.fromDecryptedPayload(jwe)) { + attr.setDirectoryId(masterKey.rootDirId()); + } + storage.withRegistry(vaults); + // TODO should be fixed with https://github.com/iterate-ch/cryptolib/pull/12, see https://github.com/cryptomator/cryptolib/pull/51/commits/1e9bd327d6a04f9eb617fe83564eae3ec65a48f8 + final BackgroundException backgroundException = assertThrows(BackgroundException.class, () -> storage.getFeature(ListService.class).list(vault.getHome().withAttributes(attr).withType(EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.vault)), new DisabledListProgressListener())); + assertEquals("File not found", backgroundException.getMessage()); + assertEquals("/" + bucketName + "/d/RK/HZLENL3PQIW6GZHE3KRRRGLFBHWHRU. Please contact your web hosting service provider for assistance.", backgroundException.getDetail()); + } + finally { + storage.withRegistry(new DefaultVaultRegistry(new DisabledPasswordCallback())); + new DeleteWorker(new DisabledLoginCallback(), + storage.getFeature(ListService.class).list(bucket, new DisabledListProgressListener()).toList().stream() + .filter(f -> storage.getFeature(Delete.class).isSupported(f)).collect(Collectors.toList()), + new DisabledProgressListener()).run(storage); + } + } - vaults.add(vault.load(storage, new DisabledPasswordCallback() { + private static @NotNull Host getMinIOBookmark() { + final Host bookmark = new Host(new S3Protocol() { @Override - public Credentials prompt(final Host bookmark, final String title, final String reason, final LoginOptions options) { - return new Credentials().withPassword(jwe); + public Scheme getScheme() { + return Scheme.http; } - })); - final PathAttributes attr = storage.getFeature(AttributesFinder.class).find(vault.getHome()); - try( - UVFMasterkey masterKey = UVFMasterkey.fromDecryptedPayload(jwe)) { - attr.setDirectoryId(masterKey.rootDirId()); - } - storage.withRegistry(vaults); - // TODO should be fixed with https://github.com/iterate-ch/cryptolib/pull/12, see https://github.com/cryptomator/cryptolib/pull/51/commits/1e9bd327d6a04f9eb617fe83564eae3ec65a48f8 - final BackgroundException backgroundException = assertThrows(BackgroundException.class, () -> storage.getFeature(ListService.class).list(vault.getHome().withAttributes(attr).withType(EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.vault)), new DisabledListProgressListener())); - assertEquals("File not found", backgroundException.getMessage()); - assertEquals("/cyberduckbucket/d/RK/HZLENL3PQIW6GZHE3KRRRGLFBHWHRU. Please contact your web hosting service provider for assistance.", backgroundException.getDetail()); + }, "localhost", 9000).withCredentials(new Credentials("minioadmin", "minioadmin")); + bookmark.setProperty("s3.bucket.virtualhost.disable", "true"); + bookmark.setDefaultPath("/"); + return bookmark; + } + + private static @NotNull S3Session getS3SessionForBookmark(final Host bookmark) throws BackgroundException { + final S3Session storage = new S3Session(bookmark); + storage.open(ProxyFactory.get(), new DisabledHostKeyCallback(), new DisabledLoginCallback(), new DisabledCancelCallback()); + storage.login(new DisabledLoginCallback() { + @Override + public Credentials prompt(final Host bookmark, final String username, final String title, final String reason, + final LoginOptions options) { + return storage.getHost().getCredentials(); + } + }, + new DisabledCancelCallback()); + return storage; } } diff --git a/s3/src/test/resources/uvf/docker-compose.yml b/s3/src/test/resources/uvf/docker-compose.yml new file mode 100644 index 00000000000..e739b4218cf --- /dev/null +++ b/s3/src/test/resources/uvf/docker-compose.yml @@ -0,0 +1,44 @@ +version: '3' + +services: + minio: + hostname: minio + image: minio/minio:latest + restart: on-failure + ports: + - "${MINIO_PORT}:${MINIO_PORT}" + - "${MINIO_CONSOLE_PORT}:${MINIO_CONSOLE_PORT}" + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + healthcheck: + test: [ "CMD", "bash", "-c", "curl -v --fail 127.0.0.1:${MINIO_PORT}/minio/health/ready" ] + interval: 5s + timeout: 1s + retries: 5 + command: server /data --console-address :9001 +# networks: +# - testContainerNetwork +# +# minio_setup: +# image: minio/mc:latest +# depends_on: +# minio: +# condition: service_healthy +# entrypoint: [ "/bin/sh","-c" ] +# command: +# - | +# set -x +# set -e +# /usr/bin/mc config host add myminio http://minio:${MINIO_PORT} minioadmin minioadmin +# +# # if container is restarted, the bucket already exists... +# /usr/bin/mc mb myminio/cyberduckbucket --with-versioning || true +# /usr/bin/mc rm --recursive --force myminio/cyberduckbucket +# +# echo "createbuckets successful" +# networks: +# - testContainerNetwork +# +#networks: +# testContainerNetwork: \ No newline at end of file From e7fb18191596b4d00ec139cd31b1438147c8b7e5 Mon Sep 17 00:00:00 2001 From: chenkins Date: Sat, 8 Mar 2025 14:37:34 +0100 Subject: [PATCH 27/39] Use expected files. --- .../cyberduck/core/cryptomator/UVFTest.java | 28 ++++++++++++------ .../GsMMTRvsuuP_6NjgRwopmWcuof-PyRQ=.uvf | Bin 68 -> 68 bytes .../RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/dir.uvf | Bin 128 -> 128 bytes .../dir.uvf | Bin 128 -> 128 bytes .../4RuVMuXcOTOfhSQZAwEV1E4XiNrMVOY=.uvf | Bin 0 -> 68 bytes .../TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/dir.uvf | Bin 0 -> 128 bytes .../0rAlpJBOfOCNkoummiK96xSJvAFLPbk=.uvf | Bin 68 -> 0 bytes .../WV/ZUTJPJT6FR7ZQRRW4FD2DPPBKJOIIF/dir.uvf | Bin 128 -> 0 bytes 8 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 s3/src/test/resources/uvf/first_vault/d/TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/4RuVMuXcOTOfhSQZAwEV1E4XiNrMVOY=.uvf create mode 100644 s3/src/test/resources/uvf/first_vault/d/TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/dir.uvf delete mode 100644 s3/src/test/resources/uvf/first_vault/d/WV/ZUTJPJT6FR7ZQRRW4FD2DPPBKJOIIF/0rAlpJBOfOCNkoummiK96xSJvAFLPbk=.uvf delete mode 100644 s3/src/test/resources/uvf/first_vault/d/WV/ZUTJPJT6FR7ZQRRW4FD2DPPBKJOIIF/dir.uvf diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java index 3ad3cbf1850..b475933f0fb 100644 --- a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java @@ -58,7 +58,7 @@ import java.util.stream.Stream; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; @Category(TestcontainerTest.class) public class UVFTest { @@ -98,8 +98,8 @@ public void listMinio() throws BackgroundException, IOException { "/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/rExOms183v5evFwgIKiW0qvbsor1Hg==.uvf/dir.uvf", "/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/dir.uvf", "/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/GsMMTRvsuuP_6NjgRwopmWcuof-PyRQ=.uvf", - "/d/WV/ZUTJPJT6FR7ZQRRW4FD2DPPBKJOIIF/0rAlpJBOfOCNkoummiK96xSJvAFLPbk=.uvf", - "/d/WV/ZUTJPJT6FR7ZQRRW4FD2DPPBKJOIIF/dir.uvf" + "/d/TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/4RuVMuXcOTOfhSQZAwEV1E4XiNrMVOY=.uvf", + "/d/TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/dir.uvf" ); final String jwe = "{\n" + " \"fileFormat\": \"AES-256-GCM-32k\",\n" + @@ -112,7 +112,8 @@ public void listMinio() throws BackgroundException, IOException { " \"initialSeed\": \"HDm38i\",\n" + " \"latestSeed\": \"QBsJFo\",\n" + " \"kdf\": \"HKDF-SHA512\",\n" + - " \"kdfSalt\": \"NIlr89R7FhochyP4yuXZmDqCnQ0dBB3UZ2D+6oiIjr8=\"\n" + + " \"kdfSalt\": \"NIlr89R7FhochyP4yuXZmDqCnQ0dBB3UZ2D+6oiIjr8=\",\n" + + " \"org.example.customfield\": 42\n" + "}"; try { @@ -128,7 +129,6 @@ public void listMinio() throws BackgroundException, IOException { out.close(); } - final VaultRegistry vaults = new DefaultVaultRegistry(new DisabledPasswordCallback()); bookmark.setDefaultPath("/" + bucketName); final UVFVault vault = new UVFVault(new DefaultPathHomeFeature(bookmark).find()); @@ -144,10 +144,20 @@ public Credentials prompt(final Host bookmark, final String title, final String attr.setDirectoryId(masterKey.rootDirId()); } storage.withRegistry(vaults); - // TODO should be fixed with https://github.com/iterate-ch/cryptolib/pull/12, see https://github.com/cryptomator/cryptolib/pull/51/commits/1e9bd327d6a04f9eb617fe83564eae3ec65a48f8 - final BackgroundException backgroundException = assertThrows(BackgroundException.class, () -> storage.getFeature(ListService.class).list(vault.getHome().withAttributes(attr).withType(EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.vault)), new DisabledListProgressListener())); - assertEquals("File not found", backgroundException.getMessage()); - assertEquals("/" + bucketName + "/d/RK/HZLENL3PQIW6GZHE3KRRRGLFBHWHRU. Please contact your web hosting service provider for assistance.", backgroundException.getDetail()); + + final Path home = vault.getHome().withAttributes(attr).withType(EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.vault)); + { + final AttributedList list = storage.getFeature(ListService.class).list(home, new DisabledListProgressListener()); + assertEquals(2, list.size()); + assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/foo.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)))); + } + { + final PathAttributes subdir = storage.getFeature(AttributesFinder.class).find(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted))); + final AttributedList list = storage.getFeature(ListService.class).list(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)).withAttributes(subdir), new DisabledListProgressListener()); + assertEquals(1, list.size()); + assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir/bar.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + } } finally { storage.withRegistry(new DefaultVaultRegistry(new DisabledPasswordCallback())); diff --git a/s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/GsMMTRvsuuP_6NjgRwopmWcuof-PyRQ=.uvf b/s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/GsMMTRvsuuP_6NjgRwopmWcuof-PyRQ=.uvf index 88a963c92e1bd545ef4d3ebdc421722ced9a50c3..14553d99bf326dc0a9d3a43aa017ea0141e21f13 100644 GIT binary patch literal 68 zcmV-K0K5Nnc4h!T8wnO#*j{rih83mjZAU>Z$&0fZIJx6U=ABGSFfYkiUl-n-;W4zA a#xVcNVt9OF9;c2EVC#o+W2{yiKK}g&r{=$k(y^4sflT@Ap!(;Wz4>(D^tOUNCjv-Elg+PZX=4 iA2m^el}Ts z*su30lDFz6!=DjJQY`RVvtS7LM{Ljd=kV2@4f+FwHI)|R_Rzm^+^!oi#4C6JJm-3` iz<$?TxYHNQCMC_q0~^dSL>oy|)@v1)a?e)ypCojfEk51= diff --git a/s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/rExOms183v5evFwgIKiW0qvbsor1Hg==.uvf/dir.uvf b/s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/rExOms183v5evFwgIKiW0qvbsor1Hg==.uvf/dir.uvf index 2dfd958d8986f9648ea95b2cf45a4dd7bf5c27cf..45ee28b88de51ac6be746438fe0ce29abe06bfa3 100644 GIT binary patch literal 128 zcmV-`0Du2=c4h!T8wnPSUV*_-V|Obgd5f*Jc&o38NI^4FSKh`h&RW6%ZRCz-4RM9= z&<=UzPlwp7j4_xD`2A2a38qkaoRO8w21*glH$94)R0%hXFG+my=ziZJJ8V11EE2|u iyO+LED#MI}M{r5F;zv#=kBR{k>?t6tZWwKJ@Qmw9ggNp6 literal 128 zcmV-`0Du2=c4h!T8wnPO-_*8tLXr8x5O{-wTx2a}%2wPdPQH!zw$RH%>}S|2=rPud zH9C2qIRb3r3P}h#heyKNHWS(5;xMzXx(6XAZ({^L>7jf;+-j)9Zw&HIF2Lb86{4J3Rs~9OAoNJ^|>Et+gqg7$5 aC)jO0bj{S4I{-qhlE<7UXzwqy9(A#@iygcG literal 0 HcmV?d00001 diff --git a/s3/src/test/resources/uvf/first_vault/d/TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/dir.uvf b/s3/src/test/resources/uvf/first_vault/d/TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/dir.uvf new file mode 100644 index 0000000000000000000000000000000000000000..2ffa72af98ca5bd8fcb9ba9a714a5f61dc3912aa GIT binary patch literal 128 zcmV-`0Du2=c4h!T8wnN-jQMQiDpc569`Z2JwP` zX+e=mD}GFgB}Bj=R?#L?!gxYPfo-+)`)*oItJQYq$ddzgf~NsS>4xL%UT?`ziV3r2 iZ@agz*(Mq3?Txmw9ds^bS*U5aWFoI35BC8#e|aUK_&zlN literal 0 HcmV?d00001 diff --git a/s3/src/test/resources/uvf/first_vault/d/WV/ZUTJPJT6FR7ZQRRW4FD2DPPBKJOIIF/0rAlpJBOfOCNkoummiK96xSJvAFLPbk=.uvf b/s3/src/test/resources/uvf/first_vault/d/WV/ZUTJPJT6FR7ZQRRW4FD2DPPBKJOIIF/0rAlpJBOfOCNkoummiK96xSJvAFLPbk=.uvf deleted file mode 100644 index dd32a527de90a84065dbb03e0773dbfedaa9d1cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmV-K0K5Nnc4h!T8wnO-Ez5eDVpq#mLnvPE_|9%7j<#~p9a~uTA78jDUNOT$U$LWE afqm(Yl!=hCpI=U;uZWNm+*y289@W+PEg#$f diff --git a/s3/src/test/resources/uvf/first_vault/d/WV/ZUTJPJT6FR7ZQRRW4FD2DPPBKJOIIF/dir.uvf b/s3/src/test/resources/uvf/first_vault/d/WV/ZUTJPJT6FR7ZQRRW4FD2DPPBKJOIIF/dir.uvf deleted file mode 100644 index c1acc94c02edd0b2f8d6ad6389ea805edb3138b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128 zcmV-`0Du2=c4h!T8wnOnL`G142;cPo8acZ7>kY5l%^zI=w+U0F7cDc4Kdo)+du?V} zg(&STm@s(Qzw-Uw=zVCO^&8tmI#$}o2;@_sX;RF({2>YY&z6if;DB)NU);cx#&K`2 iyTtfO%*9(tonCS)`Vh;>$Ycksv8jpCLzz{UJR`V=wn3Nx From 01539ae1b5b34457d6cffef175749ee15c9b8ce3 Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Sun, 9 Mar 2025 17:33:54 +0100 Subject: [PATCH 28/39] Fix rootDirId handling. --- .../core/cryptomator/AbstractVault.java | 2 ++ .../core/cryptomator/CryptoVault.java | 22 +++++-------------- .../cyberduck/core/cryptomator/UVFVault.java | 8 ++++++- .../impl/CryptoDirectoryUVFProvider.java | 4 +++- .../impl/CryptoDirectoryV6Provider.java | 3 ++- .../cyberduck/core/cryptomator/UVFTest.java | 5 ----- 6 files changed, 19 insertions(+), 25 deletions(-) diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java index cb0b492d290..9ee926cdf7c 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java @@ -289,6 +289,8 @@ public boolean contains(final Path file) { public abstract Pattern getBase64URLPattern(); + public abstract byte[] getRootDirId(); + @Override public synchronized void close() { if(this.isUnlocked()) { 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 661161fc2c3..9bbbc524b3a 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java @@ -85,10 +85,6 @@ /** * Cryptomator vault implementation */ -// 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); @@ -136,7 +132,6 @@ public CryptoVault(final Path home, final String masterkey, final String config, 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()); @@ -149,13 +144,10 @@ 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()) { @@ -238,7 +230,6 @@ 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); @@ -255,7 +246,6 @@ 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( @@ -265,8 +255,6 @@ 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)) { @@ -278,7 +266,6 @@ 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()) @@ -286,7 +273,6 @@ 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; @@ -329,7 +315,6 @@ public synchronized void close() { 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: @@ -348,7 +333,6 @@ protected CryptoDirectory createDirectoryProvider(final VaultConfig vaultConfig, } } - // UVF: extract to v6/v7/uvf, at this point we know which version protected void open(final VaultConfig vaultConfig, final CharSequence passphrase) throws BackgroundException { try { final PerpetualMasterkey masterKey = this.getMasterKey(vaultConfig.getMkfile(), passphrase); @@ -362,7 +346,6 @@ protected void open(final VaultConfig vaultConfig, final CharSequence passphrase } } - // UVF: unused?! protected void open(final VaultConfig vaultConfig, final PerpetualMasterkey masterKey) throws BackgroundException { this.vaultVersion = vaultConfig.version; final CryptorProvider provider = CryptorProvider.forScheme(vaultConfig.getCipherCombo()); @@ -456,6 +439,11 @@ public Pattern getBase64URLPattern() { return BASE64URL_PATTERN; } + @Override + public byte[] getRootDirId() { + return CryptoDirectoryV6Provider.ROOT_DIR_ID; + } + @Override public boolean equals(final Object o) { if(this == o) { diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java index 5a20913abd2..719c257220c 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -62,6 +62,7 @@ public class UVFVault extends AbstractVault { private CryptoDirectory directoryProvider; private int nonceSize; + private byte[] rootDirId; public UVFVault(final Path home) { this.home = home; @@ -88,9 +89,10 @@ public UVFVault load(final Session session, final PasswordCallback prompt) th log.debug("Initialized crypto provider {}", provider); this.cryptor = provider.provide(masterKey, FastSecureRandomProvider.get().provide()); this.fileNameCryptor = new CryptorCache(cryptor.fileNameCryptor(masterKey.firstRevision())); // TODO revision eventually depends on location - safe? - this.filenameProvider = new CryptoFilenameV7Provider(Integer.MAX_VALUE); // TODO there is no shortening in UVF defined yet + this.filenameProvider = new CryptoFilenameV7Provider(Integer.MAX_VALUE); this.directoryProvider = new CryptoDirectoryUVFProvider(this, filenameProvider, fileNameCryptor); this.nonceSize = 12; + this.rootDirId = masterKey.rootDirId(); return this; } @@ -177,6 +179,10 @@ public Pattern getBase64URLPattern() { return BASE64URL_PATTERN; } + public byte[] getRootDirId() { + return rootDirId; + } + @Override public boolean equals(final Object o) { if(this == o) { diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java index 8f7c28fc93b..328bbeeb929 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java @@ -26,16 +26,18 @@ public class CryptoDirectoryUVFProvider extends CryptoDirectoryV7Provider { private final Path home; + private final AbstractVault vault; public CryptoDirectoryUVFProvider(final AbstractVault vault, final CryptoFilename filenameProvider, final CryptorCache filenameCryptor) { super(vault, filenameProvider, filenameCryptor); this.home = vault.getHome(); + this.vault = vault; } @Override protected byte[] toDirectoryId(final Session session, final Path directory, final byte[] directoryId) throws BackgroundException { if(new SimplePathPredicate(home).test(directory)) { - return directoryId; + return vault.getRootDirId(); } return super.toDirectoryId(session, directory, directoryId); } 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 1d7e236452a..19ff6c0198b 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 @@ -46,7 +46,6 @@ public class CryptoDirectoryV6Provider implements CryptoDirectory { private static final Logger log = LogManager.getLogger(CryptoDirectoryV6Provider.class); private static final String DATA_DIR_NAME = "d"; - private static final byte[] ROOT_DIR_ID = new byte[0]; private final Path dataRoot; private final Path home; @@ -61,6 +60,8 @@ public class CryptoDirectoryV6Provider implements CryptoDirectory { private final LRUCache, byte[]> cache = LRUCache.build( PreferencesFactory.get().getInteger("cryptomator.cache.size")); + public static final byte[] ROOT_DIR_ID = new byte[0]; + public CryptoDirectoryV6Provider(final Path vault, final CryptoFilename filenameProvider, final CryptorCache filenameCryptor) { this.home = vault; this.dataRoot = new Path(vault, DATA_DIR_NAME, vault.getType()); diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java index b475933f0fb..aa5f8d1a0c6 100644 --- a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java @@ -37,7 +37,6 @@ import ch.cyberduck.test.TestcontainerTest; import org.apache.commons.io.IOUtils; -import org.cryptomator.cryptolib.api.UVFMasterkey; import org.jetbrains.annotations.NotNull; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -139,10 +138,6 @@ public Credentials prompt(final Host bookmark, final String title, final String } })); final PathAttributes attr = storage.getFeature(AttributesFinder.class).find(vault.getHome()); - try( - UVFMasterkey masterKey = UVFMasterkey.fromDecryptedPayload(jwe)) { - attr.setDirectoryId(masterKey.rootDirId()); - } storage.withRegistry(vaults); final Path home = vault.getHome().withAttributes(attr).withType(EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.vault)); From f48649895c430a4078fc6b9c40bb71f6e863bc7b Mon Sep 17 00:00:00 2001 From: chenkins Date: Sun, 9 Mar 2025 21:36:43 +0100 Subject: [PATCH 29/39] Implement loading dir.uvf using file content encryption instead of raw reading. --- .../impl/CryptoDirectoryUVFProvider.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java index 328bbeeb929..6495ea3e3f3 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java @@ -16,18 +16,34 @@ */ import ch.cyberduck.core.Path; +import ch.cyberduck.core.RandomStringService; import ch.cyberduck.core.Session; import ch.cyberduck.core.SimplePathPredicate; +import ch.cyberduck.core.UUIDRandomStringService; import ch.cyberduck.core.cryptomator.AbstractVault; +import ch.cyberduck.core.cryptomator.ContentReader; import ch.cyberduck.core.cryptomator.CryptoFilename; import ch.cyberduck.core.cryptomator.CryptorCache; import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.exception.NotfoundException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.cryptomator.cryptolib.api.FileHeader; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.EnumSet; public class CryptoDirectoryUVFProvider extends CryptoDirectoryV7Provider { + private static final Logger log = LogManager.getLogger(CryptoDirectoryUVFProvider.class); private final Path home; private final AbstractVault vault; + private final RandomStringService random + = new UUIDRandomStringService(); + public CryptoDirectoryUVFProvider(final AbstractVault vault, final CryptoFilename filenameProvider, final CryptorCache filenameCryptor) { super(vault, filenameProvider, filenameCryptor); this.home = vault.getHome(); @@ -41,4 +57,39 @@ protected byte[] toDirectoryId(final Session session, final Path directory, f } return super.toDirectoryId(session, directory, directoryId); } + + protected byte[] load(final Session session, final Path directory) throws BackgroundException { + final Path parent = this.toEncrypted(session, directory.getParent().attributes().getDirectoryId(), directory.getParent()); + final String cleartextName = directory.getName(); + final String ciphertextName = this.toEncrypted(session, parent.attributes().getDirectoryId(), cleartextName, EnumSet.of(Path.Type.directory)); + final Path metadataParent = new Path(parent, ciphertextName, EnumSet.of(Path.Type.directory)); + // Read directory id from file + try { + log.debug("Read directory ID for folder {} from {}", directory, ciphertextName); + final Path metadataFile = new Path(metadataParent, vault.getDirectoryMetadataFilename(), EnumSet.of(Path.Type.file, Path.Type.encrypted)); + final byte[] ciphertext = new ContentReader(session).readBytes(metadataFile); + // https://github.com/encryption-alliance/unified-vault-format/blob/develop/file%20name%20encryption/AES-SIV-512-B64URL.md#format-of-diruvf-and-symlinkuvf + // TODO can we not use org.cryptomator.cryptolib.v3.DirectoryContentCryptorImpl.decryptDirectoryMetadata()? DirectoryMetadataImpl is not visible and DirectoryMetadata is empty interface, so we cannot access dirId attribute. + if(ciphertext.length != 128) { + throw new IllegalArgumentException("Invalid dir.uvf length: " + ciphertext.length); + } + int headerSize = vault.getCryptor().fileHeaderCryptor().headerSize(); + ByteBuffer buffer = ByteBuffer.wrap(ciphertext); + ByteBuffer headerBuf = buffer.duplicate(); + headerBuf.position(0).limit(headerSize); + ByteBuffer contentBuf = buffer.duplicate(); + contentBuf.position(headerSize); + + FileHeader header = vault.getCryptor().fileHeaderCryptor().decryptHeader(headerBuf); + ByteBuffer plaintext = vault.getCryptor().fileContentCryptor().decryptChunk(contentBuf, 0, header, true); + assert plaintext.remaining() == 32; + byte[] dirId = new byte[32]; + plaintext.get(dirId); + return dirId; + } + catch(NotfoundException e) { + log.warn("Missing directory ID for folder {}", directory); + return random.random().getBytes(StandardCharsets.US_ASCII); + } + } } From 4d804f31c1bfe9551c7bae69b256b00a0715bd5e Mon Sep 17 00:00:00 2001 From: chenkins Date: Sun, 9 Mar 2025 21:37:18 +0100 Subject: [PATCH 30/39] Verify root dir ID. --- s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java index aa5f8d1a0c6..38195746494 100644 --- a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java @@ -37,6 +37,7 @@ import ch.cyberduck.test.TestcontainerTest; import org.apache.commons.io.IOUtils; +import org.cryptomator.cryptolib.api.UVFMasterkey; import org.jetbrains.annotations.NotNull; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -139,6 +140,9 @@ public Credentials prompt(final Host bookmark, final String title, final String })); final PathAttributes attr = storage.getFeature(AttributesFinder.class).find(vault.getHome()); storage.withRegistry(vaults); + try(final UVFMasterkey masterKey = UVFMasterkey.fromDecryptedPayload(jwe)) { + assertTrue(Arrays.equals(masterKey.rootDirId(), vault.getRootDirId())); + } final Path home = vault.getHome().withAttributes(attr).withType(EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.vault)); { @@ -149,6 +153,7 @@ public Credentials prompt(final Host bookmark, final String title, final String } { final PathAttributes subdir = storage.getFeature(AttributesFinder.class).find(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted))); + // TODO still fails as test data from org.cryptomator.cryptolib.v3.UVFIntegrationTest uses latestSeed when creating dir.uvf, hard-coded in current implementation for subdir in DirectoryMetadata subDirMetadata = dirContentCryptor.newDirectoryMetadata();) final AttributedList list = storage.getFeature(ListService.class).list(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)).withAttributes(subdir), new DisabledListProgressListener()); assertEquals(1, list.size()); assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir/bar.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); @@ -160,6 +165,7 @@ public Credentials prompt(final Host bookmark, final String title, final String storage.getFeature(ListService.class).list(bucket, new DisabledListProgressListener()).toList().stream() .filter(f -> storage.getFeature(Delete.class).isSupported(f)).collect(Collectors.toList()), new DisabledProgressListener()).run(storage); + storage.getFeature(Delete.class).delete(Collections.singletonList(bucket), new DisabledPasswordCallback(), new Delete.DisabledCallback()); } } From edf148d824b81e12c05331a855a1efc35f4c09a3 Mon Sep 17 00:00:00 2001 From: chenkins Date: Sun, 9 Mar 2025 22:15:36 +0100 Subject: [PATCH 31/39] Use revision from file header in CryptoDirectoryUVFProvider. --- .../impl/CryptoDirectoryUVFProvider.java | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java index 6495ea3e3f3..32d4b668e18 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java @@ -16,6 +16,7 @@ */ import ch.cyberduck.core.Path; +import ch.cyberduck.core.PathAttributes; import ch.cyberduck.core.RandomStringService; import ch.cyberduck.core.Session; import ch.cyberduck.core.SimplePathPredicate; @@ -32,6 +33,7 @@ import org.cryptomator.cryptolib.api.FileHeader; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.util.EnumSet; @@ -43,11 +45,15 @@ public class CryptoDirectoryUVFProvider extends CryptoDirectoryV7Provider { private final RandomStringService random = new UUIDRandomStringService(); + private final Path dataRoot; + private final CryptorCache filenameCryptor; public CryptoDirectoryUVFProvider(final AbstractVault vault, final CryptoFilename filenameProvider, final CryptorCache filenameCryptor) { super(vault, filenameProvider, filenameCryptor); + this.filenameCryptor = filenameCryptor; this.home = vault.getHome(); this.vault = vault; + this.dataRoot = new Path(home, "d", home.getType()); } @Override @@ -58,6 +64,40 @@ protected byte[] toDirectoryId(final Session session, final Path directory, f return super.toDirectoryId(session, directory, directoryId); } + @Override + public Path toEncrypted(final Session session, final byte[] directoryId, final Path directory) throws BackgroundException { + if(!directory.isDirectory()) { + throw new NotfoundException(directory.getAbsolute()); + } + if(new SimplePathPredicate(directory).test(home) || directory.isChild(home)) { + final PathAttributes attributes = new PathAttributes(directory.attributes()); + // The root of the vault is a different target directory and file ids always correspond to the metadata file + attributes.withVersionId(null); + attributes.withFileId(null); + // Remember random directory id for use in vault + final byte[] id = this.toDirectoryId(session, directory, directoryId); + log.debug("Use directory ID '{}' for folder {}", id, directory); + attributes.setDirectoryId(id); + attributes.setDecrypted(directory); + final String directoryIdHash; + if(new SimplePathPredicate(home).test(directory)) { + // TODO hard-coded to initial seed in UVFVault + directoryIdHash = filenameCryptor.hashDirectoryId(id); + } + else { + directoryIdHash = vault.getCryptor().fileNameCryptor(loadRevision(session, directory)).hashDirectoryId(id); + } + // Intermediate directory + final Path intermediate = new Path(dataRoot, directoryIdHash.substring(0, 2), dataRoot.getType()); + // Add encrypted type + final EnumSet type = EnumSet.copyOf(directory.getType()); + type.add(Path.Type.encrypted); + type.remove(Path.Type.decrypted); + return new Path(intermediate, directoryIdHash.substring(2), type, attributes); + } + throw new NotfoundException(directory.getAbsolute()); + } + protected byte[] load(final Session session, final Path directory) throws BackgroundException { final Path parent = this.toEncrypted(session, directory.getParent().attributes().getDirectoryId(), directory.getParent()); final String cleartextName = directory.getName(); @@ -80,7 +120,7 @@ protected byte[] load(final Session session, final Path directory) throws Bac ByteBuffer contentBuf = buffer.duplicate(); contentBuf.position(headerSize); - FileHeader header = vault.getCryptor().fileHeaderCryptor().decryptHeader(headerBuf); + FileHeader header = vault.getCryptor().fileHeaderCryptor(loadRevision(session, directory)).decryptHeader(headerBuf); ByteBuffer plaintext = vault.getCryptor().fileContentCryptor().decryptChunk(contentBuf, 0, header, true); assert plaintext.remaining() == 32; byte[] dirId = new byte[32]; @@ -92,4 +132,25 @@ protected byte[] load(final Session session, final Path directory) throws Bac return random.random().getBytes(StandardCharsets.US_ASCII); } } + + protected int loadRevision(final Session session, final Path directory) throws BackgroundException { + final Path parent = this.toEncrypted(session, directory.getParent().attributes().getDirectoryId(), directory.getParent()); + final String cleartextName = directory.getName(); + final String ciphertextName = this.toEncrypted(session, parent.attributes().getDirectoryId(), cleartextName, EnumSet.of(Path.Type.directory)); + final Path metadataParent = new Path(parent, ciphertextName, EnumSet.of(Path.Type.directory)); + // Read directory id from file + log.debug("Read directory ID for folder {} from {}", directory, ciphertextName); + final Path metadataFile = new Path(metadataParent, vault.getDirectoryMetadataFilename(), EnumSet.of(Path.Type.file, Path.Type.encrypted)); + final byte[] ciphertext = new ContentReader(session).readBytes(metadataFile); + // https://github.com/encryption-alliance/unified-vault-format/blob/develop/file%20name%20encryption/AES-SIV-512-B64URL.md#format-of-diruvf-and-symlinkuvf + // TODO can we not use org.cryptomator.cryptolib.v3.DirectoryContentCryptorImpl.decryptDirectoryMetadata()? DirectoryMetadataImpl is not visible and DirectoryMetadata is empty interface, so we cannot access dirId attribute. + if(ciphertext.length != 128) { + throw new IllegalArgumentException("Invalid dir.uvf length: " + ciphertext.length); + } + int headerSize = vault.getCryptor().fileHeaderCryptor().headerSize(); + ByteBuffer buffer = ByteBuffer.wrap(ciphertext); + ByteBuffer headerBuf = buffer.duplicate(); + headerBuf.position(4).limit(headerSize); + return headerBuf.order(ByteOrder.BIG_ENDIAN).getInt(); + } } From ca9b7bf7b478cc0d74cc0ccff2516b2320b46932 Mon Sep 17 00:00:00 2001 From: chenkins Date: Tue, 11 Mar 2025 10:17:50 +0100 Subject: [PATCH 32/39] Implement decrypt respecting revision. --- .../cyberduck/core/cryptomator/UVFVault.java | 116 ++++++++++++++++++ .../cyberduck/core/cryptomator/UVFTest.java | 1 - 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java index 719c257220c..85ea830c7d2 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -19,6 +19,8 @@ import ch.cyberduck.core.LoginOptions; import ch.cyberduck.core.PasswordCallback; 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.cryptomator.impl.CryptoDirectoryUVFProvider; @@ -29,16 +31,23 @@ 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.UVFMasterkey; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.text.MessageFormat; +import java.util.EnumSet; import java.util.Objects; +import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.google.common.io.BaseEncoding; + public class UVFVault extends AbstractVault { private static final Logger log = LogManager.getLogger(UVFVault.class); @@ -51,6 +60,9 @@ public class UVFVault extends AbstractVault { private static final Pattern BASE64URL_PATTERN = Pattern.compile("^([A-Za-z0-9_=-]+)" + REGULAR_FILE_EXTENSION); + // copied from AbstractVault + private static final Pattern BASE32_PATTERN = Pattern.compile("^0?(([A-Z2-7]{8})*[A-Z2-7=]{8})"); + /** * Root of vault directory */ @@ -96,6 +108,110 @@ public UVFVault load(final Session session, final PasswordCallback prompt) th return this; } + @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 defcrypted 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 : this.getBase64URLPattern(); + final Matcher m = pattern.matcher(inflated.getName()); + if(m.matches()) { + final String ciphertext = m.group(1); + try { + final CryptorCache effectivefileNameCryptor; + // / diff to AbstractVault.decrypt + final int revision = loadRevision(session, file); + effectivefileNameCryptor = new CryptorCache(this.getCryptor().fileNameCryptor(revision)); + // \ + final String cleartextFilename = effectivefileNameCryptor.decryptFilename( + this.getVersion() == VAULT_VERSION_DEPRECATED ? BaseEncoding.base32() : BaseEncoding.base64Url(), + ciphertext, file.getParent().attributes().getDirectoryId()); + 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.getHome()); + 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 int loadRevision(final Session session, final Path directory) throws BackgroundException { + // Read directory id from file + log.debug("Read directory ID from {}", directory); + final Path metadataFile = new Path(directory.getParent(), this.getDirectoryMetadataFilename(), EnumSet.of(Path.Type.file, Path.Type.encrypted)); + final byte[] ciphertext = new ContentReader(session).readBytes(metadataFile); + // https://github.com/encryption-alliance/unified-vault-format/blob/develop/file%20name%20encryption/AES-SIV-512-B64URL.md#format-of-diruvf-and-symlinkuvf + // TODO can we not use org.cryptomator.cryptolib.v3.DirectoryContentCryptorImpl.decryptDirectoryMetadata()? DirectoryMetadataImpl is not visible and DirectoryMetadata is empty interface, so we cannot access dirId attribute. + if(ciphertext.length != 128) { + throw new IllegalArgumentException("Invalid dir.uvf length: " + ciphertext.length); + } + int headerSize = this.getCryptor().fileHeaderCryptor().headerSize(); + ByteBuffer buffer = ByteBuffer.wrap(ciphertext); + ByteBuffer headerBuf = buffer.duplicate(); + headerBuf.position(4).limit(headerSize); + return headerBuf.order(ByteOrder.BIG_ENDIAN).getInt(); + } + + + // copied from AbstractVault + private boolean isDirectory(final Path p) { + if(this.getVersion() == VAULT_VERSION_DEPRECATED) { + return p.getName().startsWith(DIR_PREFIX); + } + return p.isDirectory(); + } + + // copied from AbstractVault + 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; + } + @Override public synchronized void close() { super.close(); diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java index 38195746494..8bf28a207b6 100644 --- a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java @@ -153,7 +153,6 @@ public Credentials prompt(final Host bookmark, final String title, final String } { final PathAttributes subdir = storage.getFeature(AttributesFinder.class).find(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted))); - // TODO still fails as test data from org.cryptomator.cryptolib.v3.UVFIntegrationTest uses latestSeed when creating dir.uvf, hard-coded in current implementation for subdir in DirectoryMetadata subDirMetadata = dirContentCryptor.newDirectoryMetadata();) final AttributedList list = storage.getFeature(ListService.class).list(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)).withAttributes(subdir), new DisabledListProgressListener()); assertEquals(1, list.size()); assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir/bar.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); From 7d7f454ed2e77b2ba3fb93983de64d555085bb85 Mon Sep 17 00:00:00 2001 From: chenkins Date: Tue, 11 Mar 2025 10:23:18 +0100 Subject: [PATCH 33/39] Rename. --- .../{UVFTest.java => UVFIntegrationTest.java} | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) rename s3/src/test/java/ch/cyberduck/core/cryptomator/{UVFTest.java => UVFIntegrationTest.java} (95%) diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java similarity index 95% rename from s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java rename to s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java index 8bf28a207b6..b0d026e5c68 100644 --- a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFTest.java +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java @@ -60,8 +60,12 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +/** + * Test {@link UVFVault} implementation against test data from + * org.cryptomator.cryptolib.v3.UVFIntegrationTest + */ @Category(TestcontainerTest.class) -public class UVFTest { +public class UVFIntegrationTest { private static final ComposeContainer container = new ComposeContainer( new File(AbstractAssumeRoleWithWebIdentityTest.class.getResource("/uvf/docker-compose.yml").getFile())) @@ -120,12 +124,12 @@ public void listMinio() throws BackgroundException, IOException { for(final String fi : files) { final Path file = new Path("/" + bucketName + "/" + fi, EnumSet.of(AbstractPath.Type.file)); byte[] content = new byte[1000]; - final int size = UVFTest.class.getResourceAsStream("/uvf/first_vault" + fi).read(content); + final int size = UVFIntegrationTest.class.getResourceAsStream("/uvf/first_vault" + fi).read(content); final TransferStatus transferStatus = new TransferStatus().withLength(size); transferStatus.setChecksum(storage.getFeature(Write.class).checksum(file, transferStatus).compute(new ByteArrayInputStream(content), transferStatus)); storage.getFeature(Bulk.class).pre(Transfer.Type.upload, Collections.singletonMap(new TransferItem(file), transferStatus), new DisabledConnectionCallback()); final StatusOutputStream out = storage.getFeature(Write.class).write(file, transferStatus, new DisabledConnectionCallback()); - IOUtils.copyLarge(UVFTest.class.getResourceAsStream("/uvf/first_vault" + fi), out); + IOUtils.copyLarge(UVFIntegrationTest.class.getResourceAsStream("/uvf/first_vault" + fi), out); out.close(); } From 894d5248402ef2fc21d88933cf6725168100a912 Mon Sep 17 00:00:00 2001 From: chenkins Date: Tue, 11 Mar 2025 14:06:48 +0100 Subject: [PATCH 34/39] Read file in subdir with current revision. --- .../cyberduck/core/cryptomator/UVFVault.java | 63 ++++++++++++++++++ .../impl/CryptoDirectoryUVFProvider.java | 21 ++++++ .../core/cryptomator/UVFIntegrationTest.java | 34 ++++++++-- .../5qTOPMA1BouBRhz_G7qfmKety92geI4=.uvf | Bin 0 -> 105 bytes .../6L/HPWBEU3OJP2EZUCP4CV3HHL47BXVEX/dir.uvf | Bin 0 -> 128 bytes .../GsMMTRvsuuP_6NjgRwopmWcuof-PyRQ=.uvf | Bin 68 -> 105 bytes .../RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/dir.uvf | Bin 128 -> 128 bytes .../dir.uvf | Bin 128 -> 128 bytes .../4RuVMuXcOTOfhSQZAwEV1E4XiNrMVOY=.uvf | Bin 68 -> 0 bytes .../TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/dir.uvf | Bin 128 -> 0 bytes 10 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 s3/src/test/resources/uvf/first_vault/d/6L/HPWBEU3OJP2EZUCP4CV3HHL47BXVEX/5qTOPMA1BouBRhz_G7qfmKety92geI4=.uvf create mode 100644 s3/src/test/resources/uvf/first_vault/d/6L/HPWBEU3OJP2EZUCP4CV3HHL47BXVEX/dir.uvf delete mode 100644 s3/src/test/resources/uvf/first_vault/d/TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/4RuVMuXcOTOfhSQZAwEV1E4XiNrMVOY=.uvf delete mode 100644 s3/src/test/resources/uvf/first_vault/d/TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/dir.uvf diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java index 85ea830c7d2..342484d0f86 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -176,6 +176,69 @@ public Path decrypt(final Session session, final Path file) throws Background } } + public Path encrypt(Session session, Path file, byte[] 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.getHome())) { + 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()); + // / diff to AbstractVault.encrypt + String filenameO = this.getDirectoryProvider().toEncrypted(session, parent.attributes().getDirectoryId(), file.getName(), file.getType()); + filename = ((CryptoDirectoryUVFProvider) this.getDirectoryProvider()).toEncrypted(session, file.getParent(), file.getName()); + // \ diff to AbstractVault.decrypt + } + 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.getHome().attributes().getDirectoryId(), this.getHome()); + } + 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.getHome()); + encrypted.attributes().setVault(this.getHome()); + return encrypted; + } + private int loadRevision(final Session session, final Path directory) throws BackgroundException { // Read directory id from file log.debug("Read directory ID from {}", directory); diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java index 32d4b668e18..6f42e9e3b5d 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java @@ -37,6 +37,8 @@ import java.nio.charset.StandardCharsets; import java.util.EnumSet; +import com.google.common.io.BaseEncoding; + public class CryptoDirectoryUVFProvider extends CryptoDirectoryV7Provider { private static final Logger log = LogManager.getLogger(CryptoDirectoryUVFProvider.class); @@ -47,10 +49,12 @@ public class CryptoDirectoryUVFProvider extends CryptoDirectoryV7Provider { = new UUIDRandomStringService(); private final Path dataRoot; private final CryptorCache filenameCryptor; + private final CryptoFilename filenameProvider; public CryptoDirectoryUVFProvider(final AbstractVault vault, final CryptoFilename filenameProvider, final CryptorCache filenameCryptor) { super(vault, filenameProvider, filenameCryptor); this.filenameCryptor = filenameCryptor; + this.filenameProvider = filenameProvider; this.home = vault.getHome(); this.vault = vault; this.dataRoot = new Path(home, "d", home.getType()); @@ -64,6 +68,20 @@ protected byte[] toDirectoryId(final Session session, final Path directory, f return super.toDirectoryId(session, directory, directoryId); } + // interface mismatch: we need parent path to get dirId and revision from dir.uvf + public String toEncrypted(final Session session, final Path parent, final String filename) throws BackgroundException { + if(new SimplePathPredicate(home).test(parent)) { + final String ciphertextName = filenameCryptor.encryptFilename(BaseEncoding.base64Url(), filename, vault.getRootDirId()) + vault.getRegularFileExtension(); + log.debug("Encrypted filename {} to {}", filename, ciphertextName); + return filenameProvider.deflate(session, ciphertextName); + + } + final byte[] directoryId = load(session, parent); + final String ciphertextName = vault.getCryptor().fileNameCryptor(loadRevision(session, parent)).encryptFilename(BaseEncoding.base64Url(), filename, directoryId) + vault.getRegularFileExtension(); + log.debug("Encrypted filename {} to {}", filename, ciphertextName); + return filenameProvider.deflate(session, ciphertextName); + } + @Override public Path toEncrypted(final Session session, final byte[] directoryId, final Path directory) throws BackgroundException { if(!directory.isDirectory()) { @@ -99,6 +117,9 @@ public Path toEncrypted(final Session session, final byte[] directoryId, fina } protected byte[] load(final Session session, final Path directory) throws BackgroundException { + if(new SimplePathPredicate(home).test(directory)) { + return vault.getRootDirId(); + } final Path parent = this.toEncrypted(session, directory.getParent().attributes().getDirectoryId(), directory.getParent()); final String cleartextName = directory.getName(); final String ciphertextName = this.toEncrypted(session, parent.attributes().getDirectoryId(), cleartextName, EnumSet.of(Path.Type.directory)); diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java index b0d026e5c68..14473226118 100644 --- a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java @@ -20,6 +20,7 @@ import ch.cyberduck.core.features.AttributesFinder; import ch.cyberduck.core.features.Bulk; import ch.cyberduck.core.features.Delete; +import ch.cyberduck.core.features.Read; import ch.cyberduck.core.features.Write; import ch.cyberduck.core.io.StatusOutputStream; import ch.cyberduck.core.proxy.ProxyFactory; @@ -49,6 +50,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.util.AbstractMap; import java.util.Arrays; import java.util.Collections; @@ -99,11 +101,11 @@ public void listMinio() throws BackgroundException, IOException { new S3BucketCreateService(storage).create(bucket, "us-east-1"); final List files = Arrays.asList( - "/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/rExOms183v5evFwgIKiW0qvbsor1Hg==.uvf/dir.uvf", - "/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/dir.uvf", - "/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/GsMMTRvsuuP_6NjgRwopmWcuof-PyRQ=.uvf", - "/d/TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/4RuVMuXcOTOfhSQZAwEV1E4XiNrMVOY=.uvf", - "/d/TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/dir.uvf" + "/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/rExOms183v5evFwgIKiW0qvbsor1Hg==.uvf/dir.uvf", // -> /subir + "/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/dir.uvf", // -> / + "/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/GsMMTRvsuuP_6NjgRwopmWcuof-PyRQ=.uvf", // -> /foo.txt + "/d/6L/HPWBEU3OJP2EZUCP4CV3HHL47BXVEX/5qTOPMA1BouBRhz_G7qfmKety92geI4=.uvf", // -> /subdir/bar.txt + "/d/6L/HPWBEU3OJP2EZUCP4CV3HHL47BXVEX/dir.uvf" // /subdir ); final String jwe = "{\n" + " \"fileFormat\": \"AES-256-GCM-32k\",\n" + @@ -152,14 +154,32 @@ public Credentials prompt(final Host bookmark, final String title, final String { final AttributedList list = storage.getFeature(ListService.class).list(home, new DisabledListProgressListener()); assertEquals(2, list.size()); - assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/foo.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + final Path foo = new Path("/cyberduckbucket/foo.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)); + assertTrue(Arrays.toString(list.toArray()), list.contains(foo)); assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)))); + + final byte[] buf = new byte[300]; + final TransferStatus status = new TransferStatus(); + try(final InputStream inputStream = storage.getFeature(Read.class).read(foo, status, new DisabledConnectionCallback())) { + int l = inputStream.read(buf); + assertEquals(9, l); + assertEquals("Hello Foo", new String(Arrays.copyOfRange(buf, 0, l))); + } } { final PathAttributes subdir = storage.getFeature(AttributesFinder.class).find(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted))); final AttributedList list = storage.getFeature(ListService.class).list(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)).withAttributes(subdir), new DisabledListProgressListener()); assertEquals(1, list.size()); - assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir/bar.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + final Path bar = new Path("/cyberduckbucket/subdir/bar.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)); + assertTrue(Arrays.toString(list.toArray()), list.contains(bar)); + + final byte[] buf = new byte[300]; + final TransferStatus status = new TransferStatus(); + try(final InputStream inputStream = storage.getFeature(Read.class).read(bar, status, new DisabledConnectionCallback())) { + int l = inputStream.read(buf); + assertEquals(9, l); + assertEquals("Hello Bar", new String(Arrays.copyOfRange(buf, 0, l))); + } } } finally { diff --git a/s3/src/test/resources/uvf/first_vault/d/6L/HPWBEU3OJP2EZUCP4CV3HHL47BXVEX/5qTOPMA1BouBRhz_G7qfmKety92geI4=.uvf b/s3/src/test/resources/uvf/first_vault/d/6L/HPWBEU3OJP2EZUCP4CV3HHL47BXVEX/5qTOPMA1BouBRhz_G7qfmKety92geI4=.uvf new file mode 100644 index 0000000000000000000000000000000000000000..9df3310ef3fd4eaf10b822ba6d092d66f23b2bda GIT binary patch literal 105 zcmV-v0G9uCc4h!T8wnPgdJf(12P_XRpfW{f4R^CvNP`jNB{59313GMA;}3<$1g&l{ zbMpdCq&VnU;mA7lNw}2fiD8;@P@-nwR#Vq=C=W{tc*{uo6KN3tQV z%>oD10v|3}HX4VCxciG$HaC}tdSpFPF}UCG48uPBe8Z=KpCeCcCJ-Jsz&Ugy46>Q@ iK#|St7N=W`BD7s;bE;X!%$qtVrjtEjr8x@~iHe*_05-4y literal 0 HcmV?d00001 diff --git a/s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/GsMMTRvsuuP_6NjgRwopmWcuof-PyRQ=.uvf b/s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/GsMMTRvsuuP_6NjgRwopmWcuof-PyRQ=.uvf index 14553d99bf326dc0a9d3a43aa017ea0141e21f13..d7fdea3faa41e9481f94c744f906cd22e832ab56 100644 GIT binary patch literal 105 zcmV-v0G9uCc4h!T8wnQtDz3w1$M7Z$&0fZIJx6U=ABGSFfYkiUl-n-;W4zA a#xVcNVt9O%i6zmCzT0}|Ip*VX7BL!%F>~G-+c?7rAGmzMI--xzgVdqMQ9UgH literal 128 zcmV-`0Du2=c4hz^Ik)mD%6dbS3S38?7CE!4G2#Ol=c(t|T$bV4y~d#lW)Rh{|9e}v z>F9;c2EVC#o+W2{yiKK}g&r{=$k(y^4sflT@Ap!(;Wz4>(D^tOUNCjv-Elg+PZX=4 iA71#1>rvwWswOvN2glN?q96NF8 zQ2*Fcied#iI~V?sYZ;6pC#{4qA1E=^T%k#?k4Tb8Ik07sih#K=?3?iIgjT?X%W1i+ iz+M9Fh0KmnAQ)-+D#n9JT=EUu7jX-|QX#C4io4$}DLXO% literal 128 zcmV-`0Du2=c4h!T8wnPSUV*_-V|Obgd5f*Jc&o38NI^4FSKh`h&RW6%ZRCz-4RM9= z&<=UzPlwp7j4_xD`2A2a38qkaoRO8w21*glH$94)R0%hXFG+my=ziZJJ8V11EE2|u iyO+LED#MI}M{r5F;zv#=kBR{k>?t6tZWwKJ@Qmw9ggNp6 diff --git a/s3/src/test/resources/uvf/first_vault/d/TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/4RuVMuXcOTOfhSQZAwEV1E4XiNrMVOY=.uvf b/s3/src/test/resources/uvf/first_vault/d/TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/4RuVMuXcOTOfhSQZAwEV1E4XiNrMVOY=.uvf deleted file mode 100644 index 4aaa738a57c9b9598adbbad201e6a1209cd6e52c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 zcmV-K0K5Nnc4h!T8wnN@R3pL>-j)9Zw&HIF2Lb86{4J3Rs~9OAoNJ^|>Et+gqg7$5 aC)jO0bj{S4I{-qhlE<7UXzwqy9(A#@iygcG diff --git a/s3/src/test/resources/uvf/first_vault/d/TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/dir.uvf b/s3/src/test/resources/uvf/first_vault/d/TU/EVUUXMHY2HHNQ4BLKNE3GBLEFD4YW6/dir.uvf deleted file mode 100644 index 2ffa72af98ca5bd8fcb9ba9a714a5f61dc3912aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128 zcmV-`0Du2=c4h!T8wnN-jQMQiDpc569`Z2JwP` zX+e=mD}GFgB}Bj=R?#L?!gxYPfo-+)`)*oItJQYq$ddzgf~NsS>4xL%UT?`ziV3r2 iZ@agz*(Mq3?Txmw9ds^bS*U5aWFoI35BC8#e|aUK_&zlN From 4684dbba612475116385077095f17bb851799c33 Mon Sep 17 00:00:00 2001 From: chenkins Date: Thu, 13 Mar 2025 07:26:51 +0100 Subject: [PATCH 35/39] Add test write and read file UVF. --- .../core/cryptomator/UVFIntegrationTest.java | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java index 14473226118..12bd288e9d6 100644 --- a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java @@ -38,6 +38,7 @@ import ch.cyberduck.test.TestcontainerTest; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.RandomUtils; import org.cryptomator.cryptolib.api.UVFMasterkey; import org.jetbrains.annotations.NotNull; import org.junit.AfterClass; @@ -157,14 +158,17 @@ public Credentials prompt(final Host bookmark, final String title, final String final Path foo = new Path("/cyberduckbucket/foo.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)); assertTrue(Arrays.toString(list.toArray()), list.contains(foo)); assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)))); + assertEquals("Hello Foo", readFile(storage, foo)); + } + { + final byte[] expected = writeRandomFile(storage, new Path("/cyberduckbucket/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)), 55); + final AttributedList list = storage.getFeature(ListService.class).list(home, new DisabledListProgressListener()); + assertEquals(3, list.size()); + assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/foo.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)))); - final byte[] buf = new byte[300]; - final TransferStatus status = new TransferStatus(); - try(final InputStream inputStream = storage.getFeature(Read.class).read(foo, status, new DisabledConnectionCallback())) { - int l = inputStream.read(buf); - assertEquals(9, l); - assertEquals("Hello Foo", new String(Arrays.copyOfRange(buf, 0, l))); - } + assertEquals(new String(expected), readFile(storage, new Path("/cyberduckbucket/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); } { final PathAttributes subdir = storage.getFeature(AttributesFinder.class).find(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted))); @@ -172,14 +176,7 @@ public Credentials prompt(final Host bookmark, final String title, final String assertEquals(1, list.size()); final Path bar = new Path("/cyberduckbucket/subdir/bar.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)); assertTrue(Arrays.toString(list.toArray()), list.contains(bar)); - - final byte[] buf = new byte[300]; - final TransferStatus status = new TransferStatus(); - try(final InputStream inputStream = storage.getFeature(Read.class).read(bar, status, new DisabledConnectionCallback())) { - int l = inputStream.read(buf); - assertEquals(9, l); - assertEquals("Hello Bar", new String(Arrays.copyOfRange(buf, 0, l))); - } + assertEquals("Hello Bar", readFile(storage, bar)); } } finally { @@ -217,4 +214,24 @@ public Credentials prompt(final Host bookmark, final String username, final Stri new DisabledCancelCallback()); return storage; } + + private static byte @NotNull [] writeRandomFile(final Session session, final Path file, int size) throws BackgroundException, IOException { + final byte[] content = RandomUtils.nextBytes(size); + final TransferStatus transferStatus = new TransferStatus().withLength(content.length); + transferStatus.setChecksum(session.getFeature(Write.class).checksum(file, transferStatus).compute(new ByteArrayInputStream(content), transferStatus)); + session.getFeature(Bulk.class).pre(Transfer.Type.upload, Collections.singletonMap(new TransferItem(file), transferStatus), new DisabledConnectionCallback()); + final StatusOutputStream out = session.getFeature(Write.class).write(file, transferStatus, new DisabledConnectionCallback()); + IOUtils.copyLarge(new ByteArrayInputStream(content), out); + out.close(); + return content; + } + + private static String readFile(final S3Session storage, final Path foo) throws IOException, BackgroundException { + final byte[] buf = new byte[300]; + final TransferStatus status = new TransferStatus(); + try(final InputStream inputStream = storage.getFeature(Read.class).read(foo, status, new DisabledConnectionCallback())) { + int l = inputStream.read(buf); + return new String(Arrays.copyOfRange(buf, 0, l)); + } + } } From 485f0798042dd8e39fb28caba814ae6376469ac5 Mon Sep 17 00:00:00 2001 From: chenkins Date: Thu, 13 Mar 2025 07:34:16 +0100 Subject: [PATCH 36/39] Add test write UVF ile to subdir. --- .../core/cryptomator/UVFIntegrationTest.java | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java index 12bd288e9d6..76fed283a1e 100644 --- a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java @@ -60,8 +60,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * Test {@link UVFVault} implementation against test data from @@ -127,7 +126,10 @@ public void listMinio() throws BackgroundException, IOException { for(final String fi : files) { final Path file = new Path("/" + bucketName + "/" + fi, EnumSet.of(AbstractPath.Type.file)); byte[] content = new byte[1000]; - final int size = UVFIntegrationTest.class.getResourceAsStream("/uvf/first_vault" + fi).read(content); + final int size; + try(InputStream in = UVFIntegrationTest.class.getResourceAsStream("/uvf/first_vault" + fi)) { + size = in.read(content); + } final TransferStatus transferStatus = new TransferStatus().withLength(size); transferStatus.setChecksum(storage.getFeature(Write.class).checksum(file, transferStatus).compute(new ByteArrayInputStream(content), transferStatus)); storage.getFeature(Bulk.class).pre(Transfer.Type.upload, Collections.singletonMap(new TransferItem(file), transferStatus), new DisabledConnectionCallback()); @@ -148,7 +150,7 @@ public Credentials prompt(final Host bookmark, final String title, final String final PathAttributes attr = storage.getFeature(AttributesFinder.class).find(vault.getHome()); storage.withRegistry(vaults); try(final UVFMasterkey masterKey = UVFMasterkey.fromDecryptedPayload(jwe)) { - assertTrue(Arrays.equals(masterKey.rootDirId(), vault.getRootDirId())); + assertArrayEquals(masterKey.rootDirId(), vault.getRootDirId()); } final Path home = vault.getHome().withAttributes(attr).withType(EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.vault)); @@ -158,10 +160,11 @@ public Credentials prompt(final Host bookmark, final String title, final String final Path foo = new Path("/cyberduckbucket/foo.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)); assertTrue(Arrays.toString(list.toArray()), list.contains(foo)); assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)))); + assertEquals("Hello Foo", readFile(storage, foo)); } { - final byte[] expected = writeRandomFile(storage, new Path("/cyberduckbucket/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)), 55); + final byte[] expected = writeRandomFile(storage, new Path("/cyberduckbucket/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)), 57); final AttributedList list = storage.getFeature(ListService.class).list(home, new DisabledListProgressListener()); assertEquals(3, list.size()); assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/foo.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); @@ -178,6 +181,15 @@ public Credentials prompt(final Host bookmark, final String title, final String assertTrue(Arrays.toString(list.toArray()), list.contains(bar)); assertEquals("Hello Bar", readFile(storage, bar)); } + { + final byte[] expected = writeRandomFile(storage, new Path("/cyberduckbucket/subdir/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)), 55); + final AttributedList list = storage.getFeature(ListService.class).list(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)), new DisabledListProgressListener()); + assertEquals(2, list.size()); + assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir/bar.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + + assertEquals(new String(expected), readFile(storage, new Path("/cyberduckbucket/subdir/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + } } finally { storage.withRegistry(new DefaultVaultRegistry(new DisabledPasswordCallback())); From bf67c96a8878510cc70bff4b0adb6cf31b1fbcd9 Mon Sep 17 00:00:00 2001 From: chenkins Date: Thu, 13 Mar 2025 07:47:24 +0100 Subject: [PATCH 37/39] Add test move UVF file. --- .../core/cryptomator/UVFIntegrationTest.java | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java index 76fed283a1e..1d9f704d551 100644 --- a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java @@ -20,6 +20,7 @@ import ch.cyberduck.core.features.AttributesFinder; import ch.cyberduck.core.features.Bulk; import ch.cyberduck.core.features.Delete; +import ch.cyberduck.core.features.Move; import ch.cyberduck.core.features.Read; import ch.cyberduck.core.features.Write; import ch.cyberduck.core.io.StatusOutputStream; @@ -41,8 +42,6 @@ import org.apache.commons.lang3.RandomUtils; import org.cryptomator.cryptolib.api.UVFMasterkey; import org.jetbrains.annotations.NotNull; -import org.junit.AfterClass; -import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; import org.testcontainers.containers.ComposeContainer; @@ -80,15 +79,6 @@ public class UVFIntegrationTest { ).collect(Collectors.toMap(AbstractMap.SimpleImmutableEntry::getKey, AbstractMap.SimpleImmutableEntry::getValue))) .withExposedService("minio-1", 9000, Wait.forListeningPort()); - @BeforeClass - public static void start() { - container.start(); - } - - @AfterClass - public static void shutdown() { - container.stop(); - } @Test public void listMinio() throws BackgroundException, IOException { @@ -190,6 +180,28 @@ public Credentials prompt(final Host bookmark, final String title, final String assertEquals(new String(expected), readFile(storage, new Path("/cyberduckbucket/subdir/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); } + { + storage.getFeature(Delete.class).delete(Collections.singletonList(new Path("/cyberduckbucket/subdir/bar.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted))), new DisabledPasswordCallback(), new Delete.DisabledCallback()); + final AttributedList list = storage.getFeature(ListService.class).list(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)), new DisabledListProgressListener()); + assertEquals(1, list.size()); + assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + } + { + storage.getFeature(Move.class).move( + new Path("/cyberduckbucket/foo.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)), + new Path("/cyberduckbucket/subdir/Dave.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)), + new TransferStatus(), new Delete.DisabledCallback(), new DisabledConnectionCallback() + ); + + final AttributedList listSubDir = storage.getFeature(ListService.class).list(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)), new DisabledListProgressListener()); + assertEquals(2, listSubDir.size()); + assertTrue(Arrays.toString(listSubDir.toArray()), listSubDir.contains(new Path("/cyberduckbucket/subdir/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + assertTrue(Arrays.toString(listSubDir.toArray()), listSubDir.contains(new Path("/cyberduckbucket/subdir/Dave.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + final AttributedList listHome = storage.getFeature(ListService.class).list(new Path("/cyberduckbucket/", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)), new DisabledListProgressListener()); + assertEquals(2, listHome.size()); + assertTrue(Arrays.toString(listHome.toArray()), listHome.contains(new Path("/cyberduckbucket/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + assertTrue(Arrays.toString(listHome.toArray()), listHome.contains(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)))); + } } finally { storage.withRegistry(new DefaultVaultRegistry(new DisabledPasswordCallback())); From 44d8e2ae9dc5e20f1f61e7094e42526908b5dd7a Mon Sep 17 00:00:00 2001 From: chenkins Date: Thu, 13 Mar 2025 07:59:26 +0100 Subject: [PATCH 38/39] Use set equals. --- .../core/cryptomator/UVFIntegrationTest.java | 67 ++++++++++++------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java index 1d9f704d551..d3deaa7cb16 100644 --- a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java @@ -55,6 +55,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; +import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -117,7 +118,7 @@ public void listMinio() throws BackgroundException, IOException { final Path file = new Path("/" + bucketName + "/" + fi, EnumSet.of(AbstractPath.Type.file)); byte[] content = new byte[1000]; final int size; - try(InputStream in = UVFIntegrationTest.class.getResourceAsStream("/uvf/first_vault" + fi)) { + try(final InputStream in = UVFIntegrationTest.class.getResourceAsStream("/uvf/first_vault" + fi)) { size = in.read(content); } final TransferStatus transferStatus = new TransferStatus().withLength(size); @@ -146,37 +147,44 @@ public Credentials prompt(final Host bookmark, final String title, final String final Path home = vault.getHome().withAttributes(attr).withType(EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.vault)); { final AttributedList list = storage.getFeature(ListService.class).list(home, new DisabledListProgressListener()); - assertEquals(2, list.size()); - final Path foo = new Path("/cyberduckbucket/foo.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)); - assertTrue(Arrays.toString(list.toArray()), list.contains(foo)); - assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)))); - - assertEquals("Hello Foo", readFile(storage, foo)); + assertEquals( + new HashSet<>(Arrays.asList( + new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)), + new Path("/cyberduckbucket/foo.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted))) + ), + new HashSet<>(list.toList())); + assertEquals("Hello Foo", readFile(storage, new Path("/cyberduckbucket/foo.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); } { final byte[] expected = writeRandomFile(storage, new Path("/cyberduckbucket/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)), 57); final AttributedList list = storage.getFeature(ListService.class).list(home, new DisabledListProgressListener()); - assertEquals(3, list.size()); - assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/foo.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); - assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); - assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)))); + assertEquals( + new HashSet<>(Arrays.asList( + new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)), + new Path("/cyberduckbucket/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)), + new Path("/cyberduckbucket/foo.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))), + new HashSet<>(list.toList())); assertEquals(new String(expected), readFile(storage, new Path("/cyberduckbucket/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); } { final PathAttributes subdir = storage.getFeature(AttributesFinder.class).find(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted))); final AttributedList list = storage.getFeature(ListService.class).list(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)).withAttributes(subdir), new DisabledListProgressListener()); - assertEquals(1, list.size()); - final Path bar = new Path("/cyberduckbucket/subdir/bar.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)); - assertTrue(Arrays.toString(list.toArray()), list.contains(bar)); - assertEquals("Hello Bar", readFile(storage, bar)); + assertEquals( + new HashSet<>(Collections.singletonList( + new Path("/cyberduckbucket/subdir/bar.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted))) + ), + new HashSet<>(list.toList())); + assertEquals("Hello Bar", readFile(storage, new Path("/cyberduckbucket/subdir/bar.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); } { final byte[] expected = writeRandomFile(storage, new Path("/cyberduckbucket/subdir/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)), 55); final AttributedList list = storage.getFeature(ListService.class).list(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)), new DisabledListProgressListener()); - assertEquals(2, list.size()); - assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir/bar.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); - assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + assertEquals( + new HashSet<>(Arrays.asList( + new Path("/cyberduckbucket/subdir/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)), + new Path("/cyberduckbucket/subdir/bar.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))), + new HashSet<>(list.toList())); assertEquals(new String(expected), readFile(storage, new Path("/cyberduckbucket/subdir/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); } @@ -185,6 +193,11 @@ public Credentials prompt(final Host bookmark, final String title, final String final AttributedList list = storage.getFeature(ListService.class).list(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)), new DisabledListProgressListener()); assertEquals(1, list.size()); assertTrue(Arrays.toString(list.toArray()), list.contains(new Path("/cyberduckbucket/subdir/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + assertEquals( + new HashSet<>(Collections.singletonList( + new Path("/cyberduckbucket/subdir/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)) + )), + new HashSet<>(list.toList())); } { storage.getFeature(Move.class).move( @@ -194,13 +207,19 @@ public Credentials prompt(final Host bookmark, final String title, final String ); final AttributedList listSubDir = storage.getFeature(ListService.class).list(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)), new DisabledListProgressListener()); - assertEquals(2, listSubDir.size()); - assertTrue(Arrays.toString(listSubDir.toArray()), listSubDir.contains(new Path("/cyberduckbucket/subdir/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); - assertTrue(Arrays.toString(listSubDir.toArray()), listSubDir.contains(new Path("/cyberduckbucket/subdir/Dave.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); + assertEquals( + new HashSet<>(Arrays.asList( + new Path("/cyberduckbucket/subdir/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)), + new Path("/cyberduckbucket/subdir/Dave.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted))) + ), + new HashSet<>(listSubDir.toList())); final AttributedList listHome = storage.getFeature(ListService.class).list(new Path("/cyberduckbucket/", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)), new DisabledListProgressListener()); - assertEquals(2, listHome.size()); - assertTrue(Arrays.toString(listHome.toArray()), listHome.contains(new Path("/cyberduckbucket/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)))); - assertTrue(Arrays.toString(listHome.toArray()), listHome.contains(new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted)))); + assertEquals( + new HashSet<>(Arrays.asList( + new Path("/cyberduckbucket/alice.txt", EnumSet.of(AbstractPath.Type.file, AbstractPath.Type.decrypted)), + new Path("/cyberduckbucket/subdir", EnumSet.of(AbstractPath.Type.directory, AbstractPath.Type.placeholder, AbstractPath.Type.decrypted))) + ), + new HashSet<>(listHome.toList())); } } finally { From 703d9b71866208c0bece146ffb5f3a3d160ce9db Mon Sep 17 00:00:00 2001 From: chenkins Date: Thu, 13 Mar 2025 08:00:39 +0100 Subject: [PATCH 39/39] Cleanup. --- .../ch/cyberduck/core/cryptomator/UVFIntegrationTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java index d3deaa7cb16..fa67774f1b4 100644 --- a/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java @@ -269,10 +269,10 @@ public Credentials prompt(final Host bookmark, final String username, final Stri return content; } - private static String readFile(final S3Session storage, final Path foo) throws IOException, BackgroundException { + private static String readFile(final Session session, final Path foo) throws IOException, BackgroundException { final byte[] buf = new byte[300]; final TransferStatus status = new TransferStatus(); - try(final InputStream inputStream = storage.getFeature(Read.class).read(foo, status, new DisabledConnectionCallback())) { + try(final InputStream inputStream = session.getFeature(Read.class).read(foo, status, new DisabledConnectionCallback())) { int l = inputStream.read(buf); return new String(Arrays.copyOfRange(buf, 0, l)); }