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/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/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 8875a0ac066..d78c32f369f 100644 --- a/cryptomator/pom.xml +++ b/cryptomator/pom.xml @@ -19,13 +19,13 @@ parent ch.cyberduck - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT cryptomator jar - 2.1.2.1 + 2.3.0.uvfdraft-SNAPSHOT diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java new file mode 100644 index 00000000000..9ee926cdf7c --- /dev/null +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java @@ -0,0 +1,426 @@ +package ch.cyberduck.core.cryptomator; + +/* + * Copyright (c) 2002-2025 iterate GmbH. All rights reserved. + * https://cyberduck.io/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +import ch.cyberduck.core.ListService; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.PathAttributes; +import ch.cyberduck.core.Permission; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.SimplePathPredicate; +import ch.cyberduck.core.UrlProvider; +import ch.cyberduck.core.cryptomator.features.*; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.features.*; +import ch.cyberduck.core.preferences.PreferencesFactory; +import ch.cyberduck.core.shared.DefaultTouchFeature; +import ch.cyberduck.core.transfer.TransferStatus; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.cryptomator.cryptolib.api.AuthenticationFailedException; +import org.cryptomator.cryptolib.api.Cryptor; +import org.cryptomator.cryptolib.api.FileContentCryptor; +import org.cryptomator.cryptolib.api.FileHeaderCryptor; + +import java.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})"); + + 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 State getState() { + return this.isUnlocked() ? State.open : State.closed; + } + + @Override + public long toCiphertextSize(final long cleartextFileOffset, final long cleartextFileSize) { + if(TransferStatus.UNKNOWN_LENGTH == cleartextFileSize) { + return TransferStatus.UNKNOWN_LENGTH; + } + final int headerSize; + if(0L == cleartextFileOffset) { + headerSize = this.getCryptor().fileHeaderCryptor().headerSize(); + } + else { + headerSize = 0; + } + return headerSize + this.getCryptor().fileContentCryptor().ciphertextSize(cleartextFileSize); + } + + @Override + public Path encrypt(Session session, Path file) throws BackgroundException { + return this.encrypt(session, file, file.attributes().getDirectoryId(), false); + } + + @Override + public Path encrypt(Session session, Path file, boolean metadata) throws BackgroundException { + return this.encrypt(session, file, file.attributes().getDirectoryId(), metadata); + } + + public Path encrypt(Session session, Path file, 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()); + 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.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; + } + + @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 : this.getBase64URLPattern(); + 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()); + 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 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.getHome()) || file.isChild(this.getHome()); + } + return false; + } + + public abstract String getRegularFileExtension(); + + public abstract String getDirectoryMetadataFilename(); + + public abstract String getBackupDirectoryMetadataFilename(); + + public abstract Pattern getBase64URLPattern(); + + public abstract byte[] getRootDirId(); + + @Override + public synchronized void close() { + if(this.isUnlocked()) { + if(this.getCryptor() != null) { + getCryptor().destroy(); + } + if(this.getDirectoryProvider() != null) { + this.getDirectoryProvider().destroy(); + } + if(this.getFilenameProvider() != null) { + this.getFilenameProvider().destroy(); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public T getFeature(final Session session, final Class type, final T delegate) { + if(this.isUnlocked()) { + if(type == ListService.class) { + return (T) new CryptoListService(session, (ListService) delegate, this); + } + if(type == Touch.class) { + // Use default touch feature because touch with remote implementation will not add encrypted file header + return (T) new CryptoTouchFeature(session, new DefaultTouchFeature(session._getFeature(Write.class)), session._getFeature(Write.class), this); + } + if(type == Directory.class) { + return (T) (this.getVersion() == VAULT_VERSION_DEPRECATED ? + new CryptoDirectoryV6Feature(session, (Directory) delegate, session._getFeature(Write.class), this) : + new CryptoDirectoryV7Feature(session, (Directory) delegate, session._getFeature(Write.class), this) + ); + } + if(type == Upload.class) { + return (T) new CryptoUploadFeature(session, (Upload) delegate, session._getFeature(Write.class), this); + } + if(type == Download.class) { + return (T) new CryptoDownloadFeature(session, (Download) delegate, session._getFeature(Read.class), this); + } + if(type == Read.class) { + return (T) new CryptoReadFeature(session, (Read) delegate, this); + } + if(type == Write.class) { + return (T) new CryptoWriteFeature(session, (Write) delegate, this); + } + if(type == MultipartWrite.class) { + return (T) new CryptoMultipartWriteFeature(session, (Write) delegate, this); + } + if(type == Move.class) { + return (T) (this.getVersion() == VAULT_VERSION_DEPRECATED ? + new CryptoMoveV6Feature(session, (Move) delegate, this) : + new CryptoMoveV7Feature(session, (Move) delegate, this)); + + } + if(type == AttributesFinder.class) { + return (T) new CryptoAttributesFeature(session, (AttributesFinder) delegate, this); + } + if(type == Find.class) { + return (T) new CryptoFindFeature(session, (Find) delegate, this); + } + if(type == UrlProvider.class) { + return (T) new CryptoUrlProvider(session, (UrlProvider) delegate, this); + } + if(type == FileIdProvider.class) { + return (T) new CryptoFileIdProvider(session, (FileIdProvider) delegate, this); + } + if(type == VersionIdProvider.class) { + return (T) new CryptoVersionIdProvider(session, (VersionIdProvider) delegate, this); + } + if(type == Delete.class) { + return (T) (this.getVersion() == VAULT_VERSION_DEPRECATED ? + new CryptoDeleteV6Feature(session, (Delete) delegate, this) : + new CryptoDeleteV7Feature(session, (Delete) delegate, this)); + } + if(type == Trash.class) { + return (T) (this.getVersion() == VAULT_VERSION_DEPRECATED ? + new CryptoDeleteV6Feature(session, (Delete) delegate, this) : + new CryptoDeleteV7Feature(session, (Delete) delegate, this)); + } + if(type == Symlink.class) { + return (T) new CryptoSymlinkFeature(session, (Symlink) delegate, this); + } + if(type == Headers.class) { + return (T) new CryptoHeadersFeature(session, (Headers) delegate, this); + } + if(type == Compress.class) { + return (T) new CryptoCompressFeature(session, (Compress) delegate, this); + } + if(type == Bulk.class) { + return (T) new CryptoBulkFeature(session, (Bulk) delegate, session._getFeature(Delete.class), this); + } + if(type == UnixPermission.class) { + return (T) new CryptoUnixPermission(session, (UnixPermission) delegate, this); + } + if(type == AclPermission.class) { + return (T) new CryptoAclPermission(session, (AclPermission) delegate, this); + } + if(type == Copy.class) { + return (T) new CryptoCopyFeature(session, (Copy) delegate, this); + } + if(type == Timestamp.class) { + return (T) new CryptoTimestampFeature(session, (Timestamp) delegate, this); + } + if(type == Encryption.class) { + return (T) new CryptoEncryptionFeature(session, (Encryption) delegate, this); + } + if(type == Lifecycle.class) { + return (T) new CryptoLifecycleFeature(session, (Lifecycle) delegate, this); + } + if(type == Location.class) { + return (T) new CryptoLocationFeature(session, (Location) delegate, this); + } + if(type == Lock.class) { + return (T) new CryptoLockFeature(session, (Lock) delegate, this); + } + if(type == Logging.class) { + return (T) new CryptoLoggingFeature(session, (Logging) delegate, this); + } + if(type == Redundancy.class) { + return (T) new CryptoRedundancyFeature(session, (Redundancy) delegate, this); + } + if(type == Search.class) { + return (T) new CryptoSearchFeature(session, (Search) delegate, this); + } + if(type == TransferAcceleration.class) { + return (T) new CryptoTransferAccelerationFeature<>(session, (TransferAcceleration) delegate, this); + } + if(type == Versioning.class) { + return (T) new CryptoVersioningFeature(session, (Versioning) delegate, this); + } + } + return delegate; + } +} diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/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/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/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/CryptoTransferStatus.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoTransferStatus.java index fc5e06559f8..7bdd884b96e 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoTransferStatus.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoTransferStatus.java @@ -27,9 +27,9 @@ public class CryptoTransferStatus extends ProxyTransferStatus implements StreamCancelation, StreamProgress { private static final Logger log = LogManager.getLogger(CryptoTransferStatus.class); - private final CryptoVault vault; + private final AbstractVault vault; - public CryptoTransferStatus(final CryptoVault vault, final TransferStatus proxy) { + public CryptoTransferStatus(final AbstractVault vault, final TransferStatus proxy) { super(proxy); this.vault = vault; this.withLength(vault.toCiphertextSize(proxy.getOffset(), proxy.getLength())) diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java index 42535c125eb..9bbbc524b3a 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoVault.java @@ -15,8 +15,19 @@ * GNU General Public License for more details. */ -import ch.cyberduck.core.*; -import ch.cyberduck.core.cryptomator.features.*; +import ch.cyberduck.core.Credentials; +import ch.cyberduck.core.DescriptiveUrl; +import ch.cyberduck.core.Host; +import ch.cyberduck.core.LocaleFactory; +import ch.cyberduck.core.LoginOptions; +import ch.cyberduck.core.PasswordCallback; +import ch.cyberduck.core.PasswordStore; +import ch.cyberduck.core.PasswordStoreFactory; +import ch.cyberduck.core.Path; +import ch.cyberduck.core.PathAttributes; +import ch.cyberduck.core.Session; +import ch.cyberduck.core.SimplePathPredicate; +import ch.cyberduck.core.UUIDRandomStringService; import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryV6Provider; import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryV7Provider; import ch.cyberduck.core.cryptomator.impl.CryptoFilenameV6Provider; @@ -26,10 +37,10 @@ import ch.cyberduck.core.exception.LocalAccessDeniedException; import ch.cyberduck.core.exception.LoginCanceledException; import ch.cyberduck.core.exception.NotfoundException; -import ch.cyberduck.core.features.*; +import ch.cyberduck.core.features.Directory; +import ch.cyberduck.core.features.Encryption; import ch.cyberduck.core.preferences.Preferences; import ch.cyberduck.core.preferences.PreferencesFactory; -import ch.cyberduck.core.shared.DefaultTouchFeature; import ch.cyberduck.core.shared.DefaultUrlProvider; import ch.cyberduck.core.transfer.TransferStatus; import ch.cyberduck.core.vault.DefaultVaultRegistry; @@ -38,13 +49,13 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.cryptomator.cryptolib.api.AuthenticationFailedException; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.CryptorProvider; import org.cryptomator.cryptolib.api.FileContentCryptor; import org.cryptomator.cryptolib.api.FileHeaderCryptor; import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.PerpetualMasterkey; import org.cryptomator.cryptolib.common.MasterkeyFile; import org.cryptomator.cryptolib.common.MasterkeyFileAccess; @@ -58,7 +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; @@ -68,7 +78,6 @@ import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.exceptions.SignatureVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; -import com.google.common.io.BaseEncoding; import com.google.gson.JsonParseException; import static ch.cyberduck.core.vault.DefaultVaultRegistry.DEFAULT_VAULTCONFIG_FILE_NAME; @@ -76,22 +85,23 @@ /** * Cryptomator vault implementation */ -public class CryptoVault implements Vault { +public class CryptoVault extends AbstractVault { private static final Logger log = LogManager.getLogger(CryptoVault.class); - public static final int VAULT_VERSION_DEPRECATED = 6; - public static final int VAULT_VERSION = PreferencesFactory.get().getInteger("cryptomator.vault.version"); public static final byte[] VAULT_PEPPER = PreferencesFactory.get().getProperty("cryptomator.vault.pepper").getBytes(StandardCharsets.UTF_8); - public static final String DIR_PREFIX = "0"; - - private static final Pattern BASE32_PATTERN = Pattern.compile("^0?(([A-Z2-7]{8})*[A-Z2-7=]{8})"); - private static final Pattern BASE64URL_PATTERN = Pattern.compile("^([A-Za-z0-9_=-]+).c9r"); - private static final String JSON_KEY_VAULTVERSION = "format"; private static final String JSON_KEY_CIPHERCONFIG = "cipherCombo"; private static final String JSON_KEY_SHORTENING_THRESHOLD = "shorteningThreshold"; + 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); + + private static final Pattern BASE64URL_PATTERN = Pattern.compile("^([A-Za-z0-9_=-]+)" + REGULAR_FILE_EXTENSION); + /** * Root of vault directory */ @@ -121,6 +131,7 @@ 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)); + this.pepper = pepper; // New vault home with vault flag set for internal use final EnumSet type = EnumSet.copyOf(home.getType()); @@ -150,7 +161,7 @@ public synchronized Path create(final Session session, final String region, f } final String passphrase = credentials.getPassword(); final ByteArrayOutputStream mkArray = new ByteArrayOutputStream(); - final Masterkey mk = Masterkey.generate(FastSecureRandomProvider.get().provide()); + final PerpetualMasterkey mk = Masterkey.generate(FastSecureRandomProvider.get().provide()); final MasterkeyFileAccess access = new MasterkeyFileAccess(pepper, FastSecureRandomProvider.get().provide()); final MasterkeyFile masterkeyFile; try { @@ -235,7 +246,6 @@ private VaultConfig readVaultConfig(final Session session) throws BackgroundE } } - public static VaultConfig parseVaultConfigFromJWT(final String token) { final DecodedJWT decoded = JWT.decode(token); return new VaultConfig( @@ -300,18 +310,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; } @@ -325,24 +324,19 @@ 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(this, filenameProvider, filenameCryptor); } } protected void open(final VaultConfig vaultConfig, final CharSequence passphrase) throws BackgroundException { - this.open(vaultConfig, passphrase, this.createFilenameProvider(vaultConfig), this.createDirectoryProvider(vaultConfig)); - } - - protected void open(final VaultConfig vaultConfig, final CharSequence passphrase, - final CryptoFilename filenameProvider, final CryptoDirectory directoryProvider) throws BackgroundException { try { - final Masterkey masterKey = this.getMasterKey(vaultConfig.getMkfile(), passphrase); - this.open(vaultConfig, masterKey, filenameProvider, directoryProvider); + final PerpetualMasterkey masterKey = this.getMasterKey(vaultConfig.getMkfile(), passphrase); + this.open(vaultConfig, masterKey); } catch(IllegalArgumentException | IOException e) { throw new VaultException("Failure reading key file", e); @@ -352,391 +346,102 @@ protected void open(final VaultConfig vaultConfig, final CharSequence passphrase } } - protected void open(final VaultConfig vaultConfig, final Masterkey masterKey) throws BackgroundException { - this.open(vaultConfig, masterKey, this.createFilenameProvider(vaultConfig), this.createDirectoryProvider(vaultConfig)); - } - - protected void open(final VaultConfig vaultConfig, final Masterkey masterKey, - final CryptoFilename filenameProvider, final CryptoDirectory directoryProvider) throws BackgroundException { + protected void open(final VaultConfig vaultConfig, final PerpetualMasterkey masterKey) throws BackgroundException { this.vaultVersion = vaultConfig.version; final CryptorProvider provider = CryptorProvider.forScheme(vaultConfig.getCipherCombo()); log.debug("Initialized crypto provider {}", provider); vaultConfig.verify(masterKey.getEncoded(), VAULT_VERSION); this.cryptor = provider.provide(masterKey, FastSecureRandomProvider.get().provide()); this.fileNameCryptor = new CryptorCache(cryptor.fileNameCryptor()); - this.filenameProvider = filenameProvider; - this.directoryProvider = directoryProvider; + this.filenameProvider = this.createFilenameProvider(vaultConfig); + this.directoryProvider = this.createDirectoryProvider(vaultConfig, this.filenameProvider, this.fileNameCryptor); this.nonceSize = vaultConfig.getNonceSize(); } - private Masterkey getMasterKey(final MasterkeyFile mkFile, final CharSequence passphrase) throws IOException { + private PerpetualMasterkey getMasterKey(final MasterkeyFile mkFile, final CharSequence passphrase) throws IOException { final StringWriter writer = new StringWriter(); mkFile.write(writer); return new MasterkeyFileAccess(pepper, FastSecureRandomProvider.get().provide()).load( new ByteArrayInputStream(writer.getBuffer().toString().getBytes(StandardCharsets.UTF_8)), passphrase); } - public synchronized boolean isUnlocked() { - return cryptor != null; - } - - @Override - public State getState() { - return this.isUnlocked() ? State.open : State.closed; - } - - @Override - public boolean contains(final Path file) { - if(this.isUnlocked()) { - return new SimplePathPredicate(file).test(home) || file.isChild(home); - } - return false; - } - - @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); - } - - 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); - } - - @Override - public long toCleartextSize(final long cleartextFileOffset, final long ciphertextFileSize) throws CryptoInvalidFilesizeException { - if(TransferStatus.UNKNOWN_LENGTH == ciphertextFileSize) { - return TransferStatus.UNKNOWN_LENGTH; - } - final int headerSize; - if(0L == cleartextFileOffset) { - headerSize = cryptor.fileHeaderCryptor().headerSize(); - } - else { - headerSize = 0; - } - try { - return cryptor.fileContentCryptor().cleartextSize(ciphertextFileSize - headerSize); - } - catch(AssertionError e) { - throw new CryptoInvalidFilesizeException(String.format("Encrypted file size must be at least %d bytes", headerSize)); - } - catch(IllegalArgumentException e) { - throw new CryptoInvalidFilesizeException(String.format("Invalid file size. %s", e.getMessage())); - } - } - - private Path inflate(final Session session, final Path file) throws BackgroundException { - final String fileName = file.getName(); - if(filenameProvider.isDeflated(fileName)) { - final String filename = filenameProvider.inflate(session, fileName); - return new Path(file.getParent(), filename, EnumSet.of(Path.Type.file), file.attributes()); - } - return file; - } - public Path getHome() { return home; } + @Override public Path getMasterkey() { return masterkey; } + @Override public Path getConfig() { return config; } + @Override + public int getVersion() { + return vaultVersion; + } + + @Override public FileHeaderCryptor getFileHeaderCryptor() { return cryptor.fileHeaderCryptor(); } + @Override public FileContentCryptor getFileContentCryptor() { return cryptor.fileContentCryptor(); } + @Override public CryptorCache getFileNameCryptor() { return fileNameCryptor; } + @Override public CryptoFilename getFilenameProvider() { return filenameProvider; } + @Override public CryptoDirectory getDirectoryProvider() { return directoryProvider; } + @Override + public Cryptor getCryptor() { + return cryptor; + } + + @Override public int getNonceSize() { return nonceSize; } - public int numberOfChunks(final long cleartextFileSize) { - return (int) (cleartextFileSize / cryptor.fileContentCryptor().cleartextChunkSize() + - ((cleartextFileSize % cryptor.fileContentCryptor().cleartextChunkSize() > 0) ? 1 : 0)); + @Override + public String getRegularFileExtension() { + return REGULAR_FILE_EXTENSION; } @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)); + public String getDirectoryMetadataFilename() { + return DIRECTORY_METADATA_FILENAME; + } - } - 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; + @Override + public String getBackupDirectoryMetadataFilename() { + return BACKUP_DIRECTORY_METADATA_FILENAME; + } + + @Override + public Pattern getBase64URLPattern() { + return BASE64URL_PATTERN; + } + + @Override + public byte[] getRootDirId() { + return CryptoDirectoryV6Provider.ROOT_DIR_ID; } @Override 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/UVFVault.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java new file mode 100644 index 00000000000..342484d0f86 --- /dev/null +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/UVFVault.java @@ -0,0 +1,390 @@ +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.LocaleFactory; +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; +import ch.cyberduck.core.cryptomator.impl.CryptoFilenameV7Provider; +import ch.cyberduck.core.cryptomator.random.FastSecureRandomProvider; +import ch.cyberduck.core.exception.BackgroundException; +import ch.cyberduck.core.vault.VaultCredentials; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.cryptomator.cryptolib.api.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); + + 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); + + 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 + */ + private final Path home; + + private Cryptor cryptor; + private CryptorCache fileNameCryptor; + private CryptoFilename filenameProvider; + private CryptoDirectory directoryProvider; + + private int nonceSize; + private byte[] rootDirId; + + public UVFVault(final Path home) { + this.home = home; + } + + @Override + public Path create(final Session session, final String region, final VaultCredentials credentials) throws BackgroundException { + throw new UnsupportedOperationException(); + } + + // load -> unlock -> open + @Override + public UVFVault load(final Session session, final PasswordCallback prompt) throws BackgroundException { + 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()); + this.fileNameCryptor = new CryptorCache(cryptor.fileNameCryptor(masterKey.firstRevision())); // TODO revision eventually depends on location - safe? + this.filenameProvider = new CryptoFilenameV7Provider(Integer.MAX_VALUE); + this.directoryProvider = new CryptoDirectoryUVFProvider(this, filenameProvider, fileNameCryptor); + this.nonceSize = 12; + this.rootDirId = masterKey.rootDirId(); + 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)); + } + } + + 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); + 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(); + cryptor.destroy(); + } + + @Override + public Path getMasterkey() { + //TODO: implement + return null; + } + + @Override + public Path getConfig() { + //TODO: implement + return null; + } + + @Override + public Path getHome() { + return home; + } + + @Override + public FileHeaderCryptor getFileHeaderCryptor() { + return cryptor.fileHeaderCryptor(); + } + + @Override + public FileContentCryptor getFileContentCryptor() { + return cryptor.fileContentCryptor(); + } + + @Override + public CryptorCache getFileNameCryptor() { + return fileNameCryptor; + } + + @Override + public CryptoFilename getFilenameProvider() { + return filenameProvider; + } + + @Override + public CryptoDirectory getDirectoryProvider() { + return directoryProvider; + } + + @Override + public Cryptor getCryptor() { + return cryptor; + } + + @Override + public int getNonceSize() { + return nonceSize; + } + + @Override + public int getVersion() { + return VAULT_VERSION; + } + + @Override + public 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 Pattern getBase64URLPattern() { + return BASE64URL_PATTERN; + } + + public byte[] getRootDirId() { + return rootDirId; + } + + @Override + public boolean equals(final Object o) { + if(this == o) { + return true; + } + if(!(o instanceof UVFVault)) { + return false; + } + final UVFVault that = (UVFVault) o; + return new SimplePathPredicate(home).test(that.home); + } + + @Override + public int hashCode() { + return Objects.hash(new SimplePathPredicate(home)); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("UVFVault{"); + sb.append("home=").append(home); + sb.append(", cryptor=").append(cryptor); + sb.append('}'); + return sb.toString(); + } +} diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoBulkFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoBulkFeature.java index c16210fc644..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 @@ -21,7 +21,7 @@ import ch.cyberduck.core.RandomStringService; import ch.cyberduck.core.Session; import ch.cyberduck.core.UUIDRandomStringService; -import ch.cyberduck.core.cryptomator.CryptoVault; +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator; import ch.cyberduck.core.cryptomator.random.RotatingNonceGenerator; import ch.cyberduck.core.exception.BackgroundException; @@ -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; @@ -46,9 +47,9 @@ public class CryptoBulkFeature implements Bulk { private final Session session; private final Bulk delegate; - private final CryptoVault cryptomator; + private final AbstractVault cryptomator; - public CryptoBulkFeature(final Session session, final Bulk delegate, final Delete delete, final CryptoVault cryptomator) { + public CryptoBulkFeature(final Session session, final Bulk delegate, final Delete delete, final AbstractVault cryptomator) { this.session = session; this.delegate = delegate.withDelete(cryptomator.getFeature(session, Delete.class, delete)); this.cryptomator = cryptomator; @@ -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/CryptoChecksumCompute.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoChecksumCompute.java index 51d4dc0c635..683b614fbda 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoChecksumCompute.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoChecksumCompute.java @@ -15,8 +15,8 @@ * GNU General Public License for more details. */ +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.cryptomator.CryptoOutputStream; -import ch.cyberduck.core.cryptomator.CryptoVault; import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator; import ch.cyberduck.core.cryptomator.random.RotatingNonceGenerator; import ch.cyberduck.core.exception.BackgroundException; @@ -55,11 +55,11 @@ public class CryptoChecksumCompute extends AbstractChecksumCompute { private static final Logger log = LogManager.getLogger(CryptoChecksumCompute.class); - private final CryptoVault cryptomator; + private final AbstractVault cryptomator; private final ChecksumCompute delegate; - public CryptoChecksumCompute(final ChecksumCompute delegate, final CryptoVault vault) { - this.cryptomator = vault; + public CryptoChecksumCompute(final ChecksumCompute delegate, final AbstractVault CryptoVaultInterface) { + this.cryptomator = CryptoVaultInterface; this.delegate = delegate; } diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoCopyFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoCopyFeature.java index dbff373f7a8..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.CryptoVault; +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator; import ch.cyberduck.core.cryptomator.random.RotatingNonceGenerator; import ch.cyberduck.core.exception.BackgroundException; @@ -37,11 +37,11 @@ public class CryptoCopyFeature implements Copy { private final Session session; private final Copy proxy; - private final CryptoVault vault; + private final AbstractVault vault; private Session target; - public CryptoCopyFeature(final Session session, final Copy proxy, final CryptoVault vault) { + public CryptoCopyFeature(final Session session, final Copy proxy, final AbstractVault vault) { this.session = session; this.target = session; this.proxy = proxy; diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDeleteV6Feature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDeleteV6Feature.java index 01a3ce06cf9..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.CryptoVault; import ch.cyberduck.core.exception.AccessDeniedException; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.NotfoundException; @@ -44,10 +44,10 @@ public class CryptoDeleteV6Feature implements Delete, Trash { private final Session session; private final Delete proxy; - private final CryptoVault vault; + private final AbstractVault vault; private final CryptoFilename filenameProvider; - public CryptoDeleteV6Feature(final Session session, final Delete proxy, final CryptoVault vault) { + public CryptoDeleteV6Feature(final Session session, final Delete proxy, final AbstractVault vault) { this.session = session; this.proxy = proxy; this.vault = vault; diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDeleteV7Feature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDeleteV7Feature.java index 8d8570ef479..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,14 +15,14 @@ * 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; import ch.cyberduck.core.Path; import ch.cyberduck.core.Session; +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.cryptomator.CryptoFilename; -import ch.cyberduck.core.cryptomator.CryptoVault; -import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryV7Provider; import ch.cyberduck.core.exception.AccessDeniedException; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.NotfoundException; @@ -45,10 +45,10 @@ public class CryptoDeleteV7Feature implements Delete, Trash { private final Session session; private final Delete proxy; - private final CryptoVault vault; + private final AbstractVault vault; private final CryptoFilename filenameProvider; - public CryptoDeleteV7Feature(final Session session, final Delete proxy, final CryptoVault vault) { + public CryptoDeleteV7Feature(final Session session, final Delete proxy, final AbstractVault vault) { this.session = session; this.proxy = proxy; this.vault = vault; @@ -62,8 +62,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 +87,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/CryptoDirectoryV6Feature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDirectoryV6Feature.java index f2eb8650ff8..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 @@ -19,8 +19,8 @@ import ch.cyberduck.core.RandomStringService; import ch.cyberduck.core.Session; import ch.cyberduck.core.UUIDRandomStringService; +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.cryptomator.ContentWriter; -import ch.cyberduck.core.cryptomator.CryptoVault; import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.Directory; @@ -40,11 +40,11 @@ public class CryptoDirectoryV6Feature implements Directory { private final Session session; private final Write writer; private final Directory delegate; - private final CryptoVault vault; + private final AbstractVault vault; private final RandomStringService random = new UUIDRandomStringService(); public CryptoDirectoryV6Feature(final Session session, final Directory delegate, - final Write writer, final CryptoVault cryptomator) { + final Write writer, final AbstractVault cryptomator) { this.session = session; this.writer = writer; this.delegate = delegate; @@ -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 ebf3e295402..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 @@ -19,9 +19,8 @@ import ch.cyberduck.core.RandomStringService; import ch.cyberduck.core.Session; import ch.cyberduck.core.UUIDRandomStringService; +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.cryptomator.ContentWriter; -import ch.cyberduck.core.cryptomator.CryptoVault; -import ch.cyberduck.core.cryptomator.impl.CryptoDirectoryV7Provider; import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.Directory; @@ -42,29 +41,29 @@ public class CryptoDirectoryV7Feature implements Directory { private final Session session; private final Write writer; private final Directory delegate; - private final CryptoVault vault; + private final AbstractVault vault; private final RandomStringService random = new UUIDRandomStringService(); public CryptoDirectoryV7Feature(final Session session, final Directory delegate, - final Write writer, final CryptoVault cryptomator) { + final Write writer, final AbstractVault vault) { this.session = session; this.writer = writer; this.delegate = delegate; - this.vault = cryptomator; + this.vault = vault; } @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())); 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.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/CryptoDownloadFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDownloadFeature.java index dc803e4c911..a9ce5863e45 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDownloadFeature.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDownloadFeature.java @@ -19,12 +19,11 @@ import ch.cyberduck.core.Local; import ch.cyberduck.core.Path; import ch.cyberduck.core.Session; -import ch.cyberduck.core.cryptomator.CryptoVault; +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.NotfoundException; import ch.cyberduck.core.features.Download; import ch.cyberduck.core.features.Read; -import ch.cyberduck.core.features.Vault; import ch.cyberduck.core.io.BandwidthThrottle; import ch.cyberduck.core.io.StreamListener; import ch.cyberduck.core.transfer.TransferStatus; @@ -33,9 +32,9 @@ public class CryptoDownloadFeature implements Download { private final Session session; private final Download proxy; - private final Vault vault; + private final AbstractVault vault; - public CryptoDownloadFeature(final Session session, final Download proxy, final Read reader, final CryptoVault vault) { + public CryptoDownloadFeature(final Session session, final Download proxy, final Read reader, final AbstractVault vault) { this.session = session; this.proxy = proxy.withReader(new CryptoReadFeature(session, reader, vault)); this.vault = vault; diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoEncryptionFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoEncryptionFeature.java index 9f30a97b1be..6dc07f6edf2 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoEncryptionFeature.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoEncryptionFeature.java @@ -18,7 +18,6 @@ import ch.cyberduck.core.LoginCallback; import ch.cyberduck.core.Path; import ch.cyberduck.core.Session; -import ch.cyberduck.core.cryptomator.CryptoVault; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.Encryption; import ch.cyberduck.core.features.Vault; @@ -31,7 +30,7 @@ public class CryptoEncryptionFeature implements Encryption { private final Encryption delegate; private final Vault vault; - public CryptoEncryptionFeature(final Session session, final Encryption delegate, final CryptoVault vault) { + public CryptoEncryptionFeature(final Session session, final Encryption delegate, final Vault vault) { this.session = session; this.delegate = delegate; this.vault = vault; diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV6Feature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV6Feature.java index e43afc0965d..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.CryptoVault; +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.InvalidFilenameException; import ch.cyberduck.core.features.Delete; @@ -34,9 +34,9 @@ public class CryptoMoveV6Feature implements Move { private final Session session; private final Move proxy; - private final CryptoVault vault; + private final AbstractVault vault; - public CryptoMoveV6Feature(final Session session, final Move delegate, final CryptoVault cryptomator) { + public CryptoMoveV6Feature(final Session session, final Move delegate, final AbstractVault cryptomator) { this.session = session; this.proxy = delegate; this.vault = cryptomator; diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV7Feature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMoveV7Feature.java index a0bd1c8eb7f..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 @@ -19,8 +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.impl.CryptoDirectoryV7Provider; +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.InvalidFilenameException; import ch.cyberduck.core.features.Delete; @@ -35,12 +34,12 @@ public class CryptoMoveV7Feature implements Move { private final Session session; private final Move proxy; - private final CryptoVault vault; + private final AbstractVault vault; - public CryptoMoveV7Feature(final Session session, final Move delegate, final CryptoVault cryptomator) { + public CryptoMoveV7Feature(final Session session, final Move delegate, final AbstractVault 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/features/CryptoMultipartWriteFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMultipartWriteFeature.java index 4943f838a28..f5d6de84717 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMultipartWriteFeature.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoMultipartWriteFeature.java @@ -16,18 +16,18 @@ */ import ch.cyberduck.core.Session; -import ch.cyberduck.core.cryptomator.CryptoVault; +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.features.AttributesFinder; import ch.cyberduck.core.features.Find; import ch.cyberduck.core.features.MultipartWrite; import ch.cyberduck.core.features.Write; public class CryptoMultipartWriteFeature extends CryptoWriteFeature implements MultipartWrite { - public CryptoMultipartWriteFeature(final Session session, final Write delegate, final CryptoVault vault) { + public CryptoMultipartWriteFeature(final Session session, final Write delegate, final AbstractVault vault) { super(session, delegate, vault); } - public CryptoMultipartWriteFeature(final Session session, final Write delegate, final Find finder, final AttributesFinder attributes, final CryptoVault vault) { + public CryptoMultipartWriteFeature(final Session session, final Write delegate, final Find finder, final AttributesFinder attributes, final AbstractVault vault) { super(session, delegate, vault); } } diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoReadFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoReadFeature.java index 758f6ee2db0..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.CryptoVault; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.Read; import ch.cyberduck.core.transfer.TransferStatus; @@ -36,9 +36,9 @@ public class CryptoReadFeature implements Read { private final Session session; private final Read proxy; - private final CryptoVault vault; + private final AbstractVault vault; - public CryptoReadFeature(final Session session, final Read proxy, final CryptoVault vault) { + public CryptoReadFeature(final Session session, final Read proxy, final AbstractVault vault) { this.session = session; this.proxy = proxy; this.vault = vault; diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoTimestampFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoTimestampFeature.java index 9db63f0ae3d..437a3c0697e 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoTimestampFeature.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoTimestampFeature.java @@ -17,8 +17,8 @@ import ch.cyberduck.core.Path; import ch.cyberduck.core.Session; +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.cryptomator.CryptoTransferStatus; -import ch.cyberduck.core.cryptomator.CryptoVault; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.Timestamp; import ch.cyberduck.core.transfer.TransferStatus; @@ -27,9 +27,9 @@ public class CryptoTimestampFeature implements Timestamp { private final Session session; private final Timestamp proxy; - private final CryptoVault vault; + private final AbstractVault vault; - public CryptoTimestampFeature(final Session session, final Timestamp proxy, final CryptoVault vault) { + public CryptoTimestampFeature(final Session session, final Timestamp proxy, final AbstractVault vault) { this.session = session; this.proxy = proxy; this.vault = vault; diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoTouchFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoTouchFeature.java index 238adb433b9..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.CryptoVault; +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.InvalidFilenameException; @@ -35,9 +35,9 @@ public class CryptoTouchFeature implements Touch { private final Session session; private final Touch proxy; - private final CryptoVault vault; + private final AbstractVault vault; - public CryptoTouchFeature(final Session session, final Touch proxy, final Write writer, final CryptoVault cryptomator) { + public CryptoTouchFeature(final Session session, final Touch proxy, final Write writer, final AbstractVault cryptomator) { this.session = session; this.proxy = proxy.withWriter(new CryptoWriteFeature<>(session, writer, cryptomator)); this.vault = cryptomator; diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoUploadFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoUploadFeature.java index 4c677786be3..98047737d2c 100644 --- a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoUploadFeature.java +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoUploadFeature.java @@ -20,8 +20,8 @@ import ch.cyberduck.core.Path; import ch.cyberduck.core.ProgressListener; import ch.cyberduck.core.Session; +import ch.cyberduck.core.cryptomator.AbstractVault; import ch.cyberduck.core.cryptomator.CryptoTransferStatus; -import ch.cyberduck.core.cryptomator.CryptoVault; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.features.Upload; import ch.cyberduck.core.features.Write; @@ -33,9 +33,9 @@ public class CryptoUploadFeature implements Upload { private final Session session; private final Upload proxy; - private final CryptoVault vault; + private final AbstractVault vault; - public CryptoUploadFeature(final Session session, final Upload delegate, final Write writer, final CryptoVault vault) { + public CryptoUploadFeature(final Session session, final Upload delegate, final Write writer, final AbstractVault vault) { this.session = session; this.proxy = delegate.withWriter(new CryptoWriteFeature<>(session, writer, vault)); this.vault = vault; diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoWriteFeature.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoWriteFeature.java index 7a8ea6f211b..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.CryptoVault; import ch.cyberduck.core.cryptomator.random.RandomNonceGenerator; import ch.cyberduck.core.cryptomator.random.RotatingNonceGenerator; import ch.cyberduck.core.exception.BackgroundException; @@ -42,9 +42,9 @@ public class CryptoWriteFeature implements Write { private final Session session; private final Write proxy; - private final CryptoVault vault; + private final AbstractVault vault; - public CryptoWriteFeature(final Session session, final Write proxy, final CryptoVault vault) { + public CryptoWriteFeature(final Session session, final Write proxy, final AbstractVault vault) { this.session = session; this.proxy = proxy; this.vault = vault; diff --git a/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java new file mode 100644 index 00000000000..6f42e9e3b5d --- /dev/null +++ b/cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryUVFProvider.java @@ -0,0 +1,177 @@ +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.PathAttributes; +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.ByteOrder; +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); + + private final Path home; + private final AbstractVault vault; + + private final RandomStringService random + = 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()); + } + + @Override + protected byte[] toDirectoryId(final Session session, final Path directory, final byte[] directoryId) throws BackgroundException { + if(new SimplePathPredicate(home).test(directory)) { + return vault.getRootDirId(); + } + 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()) { + 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 { + 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)); + 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(loadRevision(session, directory)).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); + } + } + + 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(); + } +} 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..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 @@ -25,17 +25,19 @@ 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; 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; @@ -44,36 +46,39 @@ 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 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")); + private final LRUCache, byte[]> cache = LRUCache.build( + PreferencesFactory.get().getInteger("cryptomator.cache.size")); - public CryptoDirectoryV6Provider(final Path vault, final CryptoVault cryptomator) { + 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()); - 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 { + 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 + cryptomator.getFileNameCryptor().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 cryptomator.getFilenameProvider().deflate(session, 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()); } @@ -83,11 +88,11 @@ 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); - 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 @@ -99,18 +104,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 { + protected 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; } @@ -122,15 +127,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)); @@ -138,11 +143,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 28b4bb6211d..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,8 +19,10 @@ 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.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; @@ -35,31 +37,28 @@ 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 CryptoVault cryptomator; + private final CryptoFilename filenameProvider; + private final CryptorCache filenameCryptor; + private final AbstractVault vault; 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 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 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; + 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) + vault.getRegularFileExtension(); 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 { + 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)); @@ -67,12 +66,12 @@ protected String 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)); - return new ContentReader(session).read(metadataFile); + 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) { 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/CryptoVaultTest.java b/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/CryptoVaultTest.java index 4bcfc9c8d12..59f21686881 100644 --- a/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/CryptoVaultTest.java +++ b/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/CryptoVaultTest.java @@ -41,7 +41,7 @@ import org.apache.commons.io.IOUtils; import org.cryptomator.cryptolib.api.CryptorProvider; -import org.cryptomator.cryptolib.api.Masterkey; +import org.cryptomator.cryptolib.api.PerpetualMasterkey; import org.cryptomator.cryptolib.common.MasterkeyFile; import org.cryptomator.cryptolib.common.MasterkeyFileAccess; import org.junit.Test; @@ -563,7 +563,7 @@ public static String createJWT(final String masterkeyCryptomator, final MasterkeyFile mkFile = MasterkeyFile.read(new StringReader(masterkeyCryptomator)); final StringWriter writer = new StringWriter(); mkFile.write(writer); - final Masterkey masterkey = new MasterkeyFileAccess(PreferencesFactory.get().getProperty("cryptomator.vault.pepper").getBytes(StandardCharsets.UTF_8), + final PerpetualMasterkey masterkey = new MasterkeyFileAccess(PreferencesFactory.get().getProperty("cryptomator.vault.pepper").getBytes(StandardCharsets.UTF_8), FastSecureRandomProvider.get().provide()).load(new ByteArrayInputStream(writer.getBuffer().toString().getBytes(StandardCharsets.UTF_8)), passphrase); final Algorithm algorithm = Algorithm.HMAC256(masterkey.getEncoded()); return JWT.create() diff --git a/cryptomator/src/test/java/ch/cyberduck/core/cryptomator/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 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..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 @@ -15,32 +15,23 @@ * 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 +40,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(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))); } @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(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))); } @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(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)); 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..60f63d696fc 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ parent Cyberduck pom - 9.1.3-SNAPSHOT + 9.1.3.uvfdraft-SNAPSHOT defaults @@ -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 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/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java new file mode 100644 index 00000000000..fa67774f1b4 --- /dev/null +++ b/s3/src/test/java/ch/cyberduck/core/cryptomator/UVFIntegrationTest.java @@ -0,0 +1,280 @@ +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.*; +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.Move; +import ch.cyberduck.core.features.Read; +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; +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 ch.cyberduck.core.worker.DeleteWorker; +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.Test; +import org.junit.experimental.categories.Category; +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.io.InputStream; +import java.util.AbstractMap; +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; + +import static org.junit.Assert.*; + +/** + * Test {@link UVFVault} implementation against test data from + * org.cryptomator.cryptolib.v3.UVFIntegrationTest + */ +@Category(TestcontainerTest.class) +public class UVFIntegrationTest { + + private static final ComposeContainer container = new ComposeContainer( + 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()); + + + @Test + public void listMinio() throws BackgroundException, IOException { + 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"); + + final List files = Arrays.asList( + "/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" + + " \"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" + + " \"org.example.customfield\": 42\n" + + "}"; + + 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; + try(final 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()); + final StatusOutputStream out = storage.getFeature(Write.class).write(file, transferStatus, new DisabledConnectionCallback()); + IOUtils.copyLarge(UVFIntegrationTest.class.getResourceAsStream("/uvf/first_vault" + fi), out); + out.close(); + } + + 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()); + storage.withRegistry(vaults); + try(final UVFMasterkey masterKey = UVFMasterkey.fromDecryptedPayload(jwe)) { + assertArrayEquals(masterKey.rootDirId(), vault.getRootDirId()); + } + + 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( + 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( + 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( + 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( + 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)))); + } + { + 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)))); + 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( + 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( + 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( + 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 { + 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); + storage.getFeature(Delete.class).delete(Collections.singletonList(bucket), new DisabledPasswordCallback(), new Delete.DisabledCallback()); + } + } + + private static @NotNull Host getMinIOBookmark() { + final Host bookmark = new Host(new S3Protocol() { + @Override + public Scheme getScheme() { + return Scheme.http; + } + }, "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; + } + + 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 Session session, final Path foo) throws IOException, BackgroundException { + final byte[] buf = new byte[300]; + final TransferStatus status = new TransferStatus(); + 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)); + } + } +} 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 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 00000000000..9df3310ef3f Binary files /dev/null and b/s3/src/test/resources/uvf/first_vault/d/6L/HPWBEU3OJP2EZUCP4CV3HHL47BXVEX/5qTOPMA1BouBRhz_G7qfmKety92geI4=.uvf differ diff --git a/s3/src/test/resources/uvf/first_vault/d/6L/HPWBEU3OJP2EZUCP4CV3HHL47BXVEX/dir.uvf b/s3/src/test/resources/uvf/first_vault/d/6L/HPWBEU3OJP2EZUCP4CV3HHL47BXVEX/dir.uvf new file mode 100644 index 00000000000..378fce0a7d2 Binary files /dev/null and b/s3/src/test/resources/uvf/first_vault/d/6L/HPWBEU3OJP2EZUCP4CV3HHL47BXVEX/dir.uvf differ 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 00000000000..d7fdea3faa4 Binary files /dev/null and b/s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/GsMMTRvsuuP_6NjgRwopmWcuof-PyRQ=.uvf differ 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 00000000000..d947abc9558 Binary files /dev/null and b/s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/dir.uvf differ 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 00000000000..84ec139b177 Binary files /dev/null and b/s3/src/test/resources/uvf/first_vault/d/RZ/K7ZH7KBXULNEKBMGX3CU42PGUIAIX4/rExOms183v5evFwgIKiW0qvbsor1Hg==.uvf/dir.uvf differ 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/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); 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