diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java index dc0f0c9d4..262e1d1a1 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java @@ -5,7 +5,7 @@ public final class Constants { private Constants() { } - static final Integer CURRENT_VAULT_VERSION = 3; + static final Integer CURRENT_VAULT_VERSION = 4; public static final int PAYLOAD_SIZE = 32 * 1024; public static final int NONCE_SIZE = 16; diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java index 4b977d44d..6833d1b9a 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java @@ -26,7 +26,8 @@ import org.cryptomator.siv.SivMode; class FilenameCryptorImpl implements FilenameCryptor { private static final BaseNCodec BASE32 = new Base32(); - private static final Pattern BASE32_PATTERN = Pattern.compile("([A-Z0-9]{8})*[A-Z0-9=]{8}"); + // https://tools.ietf.org/html/rfc4648#section-6 + private static final Pattern BASE32_PATTERN = Pattern.compile("([A-Z2-7]{8})*[A-Z2-7=]{8}"); private static final ThreadLocal SHA1 = new ThreadLocalSha1(); private static final ThreadLocal AES_SIV = new ThreadLocal() { @Override diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java index 11bc14f77..19dceb326 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/ConflictResolver.java @@ -1,6 +1,6 @@ package org.cryptomator.filesystem.crypto; -import static org.cryptomator.filesystem.crypto.Constants.DIR_SUFFIX; +import static org.cryptomator.filesystem.crypto.Constants.DIR_PREFIX; import java.util.Optional; import java.util.UUID; @@ -32,7 +32,7 @@ final class ConflictResolver { } public File resolveIfNecessary(File file) { - Matcher m = encryptedNamePattern.matcher(StringUtils.removeEnd(file.name(), DIR_SUFFIX)); + Matcher m = encryptedNamePattern.matcher(StringUtils.removeStart(file.name(), DIR_PREFIX)); if (m.matches()) { // full match, use file as is return file; @@ -47,11 +47,11 @@ final class ConflictResolver { private File resolveConflict(File conflictingFile, MatchResult matchResult) { String ciphertext = matchResult.group(); - boolean isDirectory = conflictingFile.name().substring(matchResult.end()).startsWith(DIR_SUFFIX); + boolean isDirectory = conflictingFile.name().startsWith(DIR_PREFIX); Optional cleartext = nameDecryptor.apply(ciphertext); if (cleartext.isPresent()) { Folder folder = conflictingFile.parent().get(); - File canonicalFile = folder.file(isDirectory ? ciphertext + DIR_SUFFIX : ciphertext); + File canonicalFile = folder.file(isDirectory ? DIR_PREFIX + ciphertext : ciphertext); if (canonicalFile.exists()) { // there must not be two directories pointing to the same directory id. In this case no human interaction is needed to resolve this conflict: if (isDirectory && FileContents.UTF_8.readContents(canonicalFile).equals(FileContents.UTF_8.readContents(conflictingFile))) { @@ -66,7 +66,7 @@ final class ConflictResolver { conflictId = createConflictId(); String alternativeCleartext = cleartext.get() + " (Conflict " + conflictId + ")"; String alternativeCiphertext = nameEncryptor.apply(alternativeCleartext).get(); - alternativeFile = folder.file(isDirectory ? alternativeCiphertext + DIR_SUFFIX : alternativeCiphertext); + alternativeFile = folder.file(isDirectory ? DIR_PREFIX + alternativeCiphertext : alternativeCiphertext); } while (alternativeFile.exists()); LOG.info("Detected conflict {}:\n{}\n{}", conflictId, canonicalFile, conflictingFile); conflictingFile.moveTo(alternativeFile); diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Constants.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Constants.java index 7e6d31af0..78a745b71 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Constants.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Constants.java @@ -11,6 +11,6 @@ public final class Constants { static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; static final String MASTERKEY_BACKUP_FILENAME = "masterkey.cryptomator.bkup"; - static final String DIR_SUFFIX = "_"; + static final String DIR_PREFIX = "0"; } diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java index 10e4289a6..b66721ca2 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java @@ -10,7 +10,7 @@ package org.cryptomator.filesystem.crypto; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.cryptomator.filesystem.crypto.Constants.DIR_SUFFIX; +import static org.cryptomator.filesystem.crypto.Constants.DIR_PREFIX; import java.io.FileNotFoundException; import java.io.UncheckedIOException; @@ -54,9 +54,9 @@ class CryptoFolder extends CryptoNode implements Folder { @Override protected Optional encryptedName() { if (parent().isPresent()) { - return parent().get().encryptChildName(name()).map(s -> s + DIR_SUFFIX); + return parent().get().encryptChildName(name()).map(s -> DIR_PREFIX + s); } else { - return Optional.of(cryptor.getFilenameCryptor().encryptFilename(name()) + DIR_SUFFIX); + return Optional.of(DIR_PREFIX + cryptor.getFilenameCryptor().encryptFilename(name())); } } @@ -121,20 +121,20 @@ class CryptoFolder extends CryptoNode implements Folder { @Override public Stream files() { - return nonConflictingFiles().map(File::name).filter(endsWithDirSuffix().negate()).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::file); + return nonConflictingFiles().map(File::name).filter(startsWithDirPrefix().negate()).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::file); } @Override public Stream folders() { - return nonConflictingFiles().map(File::name).filter(endsWithDirSuffix()).map(this::removeDirSuffix).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::folder); + return nonConflictingFiles().map(File::name).filter(startsWithDirPrefix()).map(this::removeDirPrefix).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::folder); } - private Predicate endsWithDirSuffix() { - return (String encryptedFolderName) -> StringUtils.endsWith(encryptedFolderName, DIR_SUFFIX); + private Predicate startsWithDirPrefix() { + return (String encryptedFolderName) -> StringUtils.startsWith(encryptedFolderName, DIR_PREFIX); } - private String removeDirSuffix(String encryptedFolderName) { - return StringUtils.removeEnd(encryptedFolderName, DIR_SUFFIX); + private String removeDirPrefix(String encryptedFolderName) { + return StringUtils.removeStart(encryptedFolderName, DIR_PREFIX); } @Override diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/ConflictResolverTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/ConflictResolverTest.java index b99a000b4..bd1cf3b51 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/ConflictResolverTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/filesystem/crypto/ConflictResolverTest.java @@ -46,7 +46,7 @@ public class ConflictResolverTest { unrelatedFile = Mockito.mock(File.class); String canonicalFileName = encode.apply("test name").get(); - String canonicalFolderName = canonicalFileName + Constants.DIR_SUFFIX; + String canonicalFolderName = Constants.DIR_PREFIX + canonicalFileName; String conflictingFileName = canonicalFileName + " (version 2)"; String conflictingFolderName = canonicalFolderName + " (version 2)"; String unrelatedName = "notBa$e32!"; @@ -70,6 +70,7 @@ public class ConflictResolverTest { Mockito.doReturn(Optional.of(folder)).when(unrelatedFile).parent(); Mockito.when(folder.file(Mockito.startsWith(canonicalFileName.substring(0, 8)))).thenReturn(resolved); + Mockito.when(folder.file(Mockito.startsWith(canonicalFolderName.substring(0, 8)))).thenReturn(resolved); Mockito.when(folder.file(canonicalFileName)).thenReturn(canonicalFile); Mockito.when(folder.file(canonicalFolderName)).thenReturn(canonicalFolder); Mockito.when(folder.file(conflictingFileName)).thenReturn(conflictingFile);