diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java index b0c679438..766cdd9cc 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java @@ -331,7 +331,7 @@ void createDirectory(CryptoPath cleartextDir, FileAttribute... attrs) throws // create dir if and only if the dirFile has been created right now (not if it has been created before): try { Files.createDirectories(ciphertextDir.path()); - dirIdBackup.execute(ciphertextDir); + dirIdBackup.write(ciphertextDir); ciphertextPath.persistLongFileName(); } catch (IOException e) { // make sure there is no orphan dir file: diff --git a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java index 1360d6d73..6d4abd45f 100644 --- a/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java +++ b/src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java @@ -155,7 +155,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope Path vaultCipherRootPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2)); Files.createDirectories(vaultCipherRootPath); // create dirId backup: - DirectoryIdBackup.backupManually(cryptor, new CiphertextDirectory(Constants.ROOT_DIR_ID, vaultCipherRootPath)); + DirectoryIdBackup.write(cryptor, new CiphertextDirectory(Constants.ROOT_DIR_ID, vaultCipherRootPath)); } finally { Arrays.fill(rawKey, (byte) 0x00); } diff --git a/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java b/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java index 6dc2f7fe5..5be4f60fb 100644 --- a/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java +++ b/src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java @@ -1,7 +1,9 @@ package org.cryptomator.cryptofs; import org.cryptomator.cryptofs.common.Constants; +import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.Cryptor; +import org.cryptomator.cryptolib.common.DecryptingReadableByteChannel; import org.cryptomator.cryptolib.common.EncryptingWritableByteChannel; import javax.inject.Inject; @@ -10,15 +12,16 @@ import java.nio.channels.ByteChannel; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.StandardOpenOption; /** - * Single purpose class to back up the directory id of an encrypted directory when it is created. + * Single purpose class to read or write the directory id backup of an encrypted directory. */ @CryptoFileSystemScoped public class DirectoryIdBackup { - private Cryptor cryptor; + private final Cryptor cryptor; @Inject public DirectoryIdBackup(Cryptor cryptor) { @@ -26,15 +29,15 @@ public DirectoryIdBackup(Cryptor cryptor) { } /** - * Performs the backup operation for the given {@link CiphertextDirectory} object. + * Writes the dirId backup file for the {@link CiphertextDirectory} object. *

- * The directory id is written via an encrypting channel to the file {@link CiphertextDirectory#path()} /{@value Constants#DIR_BACKUP_FILE_NAME}. + * The directory id is written via an encrypting channel to the file {@link CiphertextDirectory#path()}.resolve({@value Constants#DIR_ID_BACKUP_FILE_NAME}); * * @param ciphertextDirectory The cipher dir object containing the dir id and the encrypted content root * @throws IOException if an IOException is raised during the write operation */ - public void execute(CiphertextDirectory ciphertextDirectory) throws IOException { - try (var channel = Files.newByteChannel(ciphertextDirectory.path().resolve(Constants.DIR_BACKUP_FILE_NAME), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); // + public void write(CiphertextDirectory ciphertextDirectory) throws IOException { + try (var channel = Files.newByteChannel(getBackupFilePath(ciphertextDirectory.path()), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); // var encryptingChannel = wrapEncryptionAround(channel, cryptor)) { encryptingChannel.write(ByteBuffer.wrap(ciphertextDirectory.dirId().getBytes(StandardCharsets.US_ASCII))); } @@ -43,16 +46,65 @@ public void execute(CiphertextDirectory ciphertextDirectory) throws IOException /** * Static method to explicitly back up the directory id for a specified ciphertext directory. * - * @param cryptor The cryptor to be used + * @param cryptor The cryptor to be used for encryption * @param ciphertextDirectory A {@link CiphertextDirectory} for which the dirId should be back up'd. * @throws IOException when the dirId file already exists, or it cannot be written to. */ - public static void backupManually(Cryptor cryptor, CiphertextDirectory ciphertextDirectory) throws IOException { - new DirectoryIdBackup(cryptor).execute(ciphertextDirectory); + public static void write(Cryptor cryptor, CiphertextDirectory ciphertextDirectory) throws IOException { + new DirectoryIdBackup(cryptor).write(ciphertextDirectory); } - static EncryptingWritableByteChannel wrapEncryptionAround(ByteChannel channel, Cryptor cryptor) { + /** + * Reads the dirId backup file and retrieves the directory id from it. + * + * @param ciphertextContentDir path of a ciphertext content directory + * @return a byte array containing the directory id + * @throws IOException if the dirId backup file cannot be read + * @throws CryptoException if the content of dirId backup file cannot be decrypted/authenticated + * @throws IllegalStateException if the directory id exceeds {@value Constants#MAX_DIR_ID_LENGTH} chars + */ + public byte[] read(Path ciphertextContentDir) throws IOException, CryptoException, IllegalStateException { + var dirIdBackupFile = getBackupFilePath(ciphertextContentDir); + var dirIdBuffer = ByteBuffer.allocate(Constants.MAX_DIR_ID_LENGTH + 1); //a dir id contains at most 36 ascii chars, we add for security checks one more + + try (var channel = Files.newByteChannel(dirIdBackupFile, StandardOpenOption.READ); // + var decryptingChannel = wrapDecryptionAround(channel, cryptor)) { + int read = decryptingChannel.read(dirIdBuffer); + if (read < 0 || read > Constants.MAX_DIR_ID_LENGTH) { + throw new IllegalStateException("Read directory id exceeds the maximum length of %d characters".formatted(Constants.MAX_DIR_ID_LENGTH)); + } + } + + var dirId = new byte[dirIdBuffer.position()]; + dirIdBuffer.get(0, dirId); + return dirId; + } + + /** + * Static method to explicitly retrieve the directory id of a ciphertext directory from the dirId backup file + * + * @param cryptor The cryptor to be used for decryption + * @param ciphertextContentDir path of a ciphertext content directory + * @return a byte array containing the directory id + * @throws IOException if the dirId backup file cannot be read + * @throws CryptoException if the content of dirId backup file cannot be decrypted/authenticated + * @throws IllegalStateException if the directory id exceeds {@value Constants#MAX_DIR_ID_LENGTH} chars + */ + public static byte[] read(Cryptor cryptor, Path ciphertextContentDir) throws IOException, CryptoException, IllegalStateException { + return new DirectoryIdBackup(cryptor).read(ciphertextContentDir); + } + + + private static Path getBackupFilePath(Path ciphertextContentDir) { + return ciphertextContentDir.resolve(Constants.DIR_ID_BACKUP_FILE_NAME); + } + + DecryptingReadableByteChannel wrapDecryptionAround(ByteChannel channel, Cryptor cryptor) { + return new DecryptingReadableByteChannel(channel, cryptor, true); + } + + EncryptingWritableByteChannel wrapEncryptionAround(ByteChannel channel, Cryptor cryptor) { return new EncryptingWritableByteChannel(channel, cryptor); } } diff --git a/src/main/java/org/cryptomator/cryptofs/common/Constants.java b/src/main/java/org/cryptomator/cryptofs/common/Constants.java index 2df843265..b944fac66 100644 --- a/src/main/java/org/cryptomator/cryptofs/common/Constants.java +++ b/src/main/java/org/cryptomator/cryptofs/common/Constants.java @@ -25,10 +25,10 @@ private Constants() { public static final String SYMLINK_FILE_NAME = "symlink.c9r"; public static final String CONTENTS_FILE_NAME = "contents.c9r"; public static final String INFLATED_FILE_NAME = "name.c9s"; - public static final String DIR_BACKUP_FILE_NAME = "dirid.c9r"; + public static final String DIR_ID_BACKUP_FILE_NAME = "dirid.c9r"; public static final int MAX_SYMLINK_LENGTH = 32767; // max path length on NTFS and FAT32: 32k-1 - public static final int MAX_DIR_FILE_LENGTH = 36; // UUIDv4: hex-encoded 16 byte int + 4 hyphens = 36 ASCII chars + public static final int MAX_DIR_ID_LENGTH = 36; // UUIDv4: hex-encoded 16 byte int + 4 hyphens = 36 ASCII chars public static final int MIN_CIPHER_NAME_LENGTH = 26; //rounded up base64url encoded (16 bytes IV + 0 bytes empty string) + file suffix = 26 ASCII chars public static final String SEPARATOR = "/"; diff --git a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java index 957ad5552..4a7db9464 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java @@ -21,7 +21,7 @@ import java.util.stream.Stream; import static org.cryptomator.cryptofs.common.Constants.DIR_FILE_NAME; -import static org.cryptomator.cryptofs.common.Constants.MAX_DIR_FILE_LENGTH; +import static org.cryptomator.cryptofs.common.Constants.MAX_DIR_ID_LENGTH; import static org.cryptomator.cryptofs.common.Constants.MAX_SYMLINK_LENGTH; import static org.cryptomator.cryptofs.common.Constants.SYMLINK_FILE_NAME; @@ -127,7 +127,7 @@ private boolean resolveConflictTrivially(Path canonicalPath, Path conflictingPat if (!Files.exists(canonicalPath)) { Files.move(conflictingPath, canonicalPath); // boom. conflict solved. return true; - } else if (hasSameFileContent(conflictingPath.resolve(DIR_FILE_NAME), canonicalPath.resolve(DIR_FILE_NAME), MAX_DIR_FILE_LENGTH)) { + } else if (hasSameFileContent(conflictingPath.resolve(DIR_FILE_NAME), canonicalPath.resolve(DIR_FILE_NAME), MAX_DIR_ID_LENGTH)) { LOG.info("Removing conflicting directory {} (identical to {})", conflictingPath, canonicalPath); MoreFiles.deleteRecursively(conflictingPath, RecursiveDeleteOption.ALLOW_INSECURE); return true; diff --git a/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFilters.java b/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFilters.java index 13f87c112..413601d0f 100644 --- a/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFilters.java +++ b/src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFilters.java @@ -7,6 +7,6 @@ public interface DirectoryStreamFilters { - static DirectoryStream.Filter EXCLUDE_DIR_ID_BACKUP = p -> !p.equals(p.resolveSibling(Constants.DIR_BACKUP_FILE_NAME)); + static DirectoryStream.Filter EXCLUDE_DIR_ID_BACKUP = p -> !p.equals(p.resolveSibling(Constants.DIR_ID_BACKUP_FILE_NAME)); } diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/DirIdCheck.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/DirIdCheck.java index f283d3121..f553a9f58 100644 --- a/src/main/java/org/cryptomator/cryptofs/health/dirid/DirIdCheck.java +++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/DirIdCheck.java @@ -62,7 +62,7 @@ public void check(Path pathToVault, VaultConfig config, Masterkey masterkey, Cry if (foundDir) { iter.remove(); var expectedDirVaultRel = Path.of(Constants.DATA_DIR_NAME).resolve(expectedDir); - if (Files.exists(pathToVault.resolve(expectedDirVaultRel).resolve(Constants.DIR_BACKUP_FILE_NAME))) { + if (Files.exists(pathToVault.resolve(expectedDirVaultRel).resolve(Constants.DIR_ID_BACKUP_FILE_NAME))) { resultCollector.accept(new HealthyDir(dirId, dirFile, expectedDirVaultRel)); } else { resultCollector.accept(new MissingDirIdBackup(dirId, expectedDirVaultRel)); @@ -116,7 +116,7 @@ private FileVisitResult visitDirFile(Path dirFile, BasicFileAttributes attrs) th return FileVisitResult.CONTINUE; } - if (attrs.size() > Constants.MAX_DIR_FILE_LENGTH) { + if (attrs.size() > Constants.MAX_DIR_ID_LENGTH) { LOG.warn("Encountered dir.c9r file of size {}", attrs.size()); resultCollector.accept(new ObeseDirFile(dirFile, attrs.size())); } else if (attrs.size() == 0) { diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java index 28cd36144..0f9df9681 100644 --- a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java +++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java @@ -51,7 +51,7 @@ void fix(Path pathToVault, Cryptor cryptor) throws IOException { var dirIdHash = cryptor.fileNameCryptor().hashDirectoryId(dirId); Path dirPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirIdHash.substring(0, 2)).resolve(dirIdHash.substring(2, 32)); Files.createDirectories(dirPath); - DirectoryIdBackup.backupManually(cryptor, new CiphertextDirectory(dirId, dirPath)); + DirectoryIdBackup.write(cryptor, new CiphertextDirectory(dirId, dirPath)); } @Override diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java index 9002ba94b..9512d5e69 100644 --- a/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java +++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java @@ -12,7 +12,7 @@ import java.util.Optional; /** - * The dir id backup file {@value org.cryptomator.cryptofs.common.Constants#DIR_BACKUP_FILE_NAME} is missing. + * The dir id backup file {@value org.cryptomator.cryptofs.common.Constants#DIR_ID_BACKUP_FILE_NAME} is missing. */ public record MissingDirIdBackup(String dirId, Path contentDir) implements DiagnosticResult { @@ -29,7 +29,7 @@ public String toString() { //visible for testing void fix(Path pathToVault, Cryptor cryptor) throws IOException { Path absCipherDir = pathToVault.resolve(contentDir); - DirectoryIdBackup.backupManually(cryptor, new CiphertextDirectory(dirId, absCipherDir)); + DirectoryIdBackup.write(cryptor, new CiphertextDirectory(dirId, absCipherDir)); } @Override diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/ObeseDirFile.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/ObeseDirFile.java index c6b019b06..de6edbaa2 100644 --- a/src/main/java/org/cryptomator/cryptofs/health/dirid/ObeseDirFile.java +++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/ObeseDirFile.java @@ -28,7 +28,7 @@ public Severity getSeverity() { @Override public String toString() { - return String.format("Unexpected file size of %s: %d should be ≤ %d", dirFile, size, Constants.MAX_DIR_FILE_LENGTH); + return String.format("Unexpected file size of %s: %d should be ≤ %d", dirFile, size, Constants.MAX_DIR_ID_LENGTH); } @Override diff --git a/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java b/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java index 350616a96..d8907406f 100644 --- a/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java +++ b/src/main/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDir.java @@ -8,17 +8,15 @@ import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptofs.health.api.DiagnosticResult; import org.cryptomator.cryptolib.api.AuthenticationFailedException; +import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.FileNameCryptor; import org.cryptomator.cryptolib.api.Masterkey; -import org.cryptomator.cryptolib.common.ByteBuffers; -import org.cryptomator.cryptolib.common.DecryptingReadableByteChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.channels.ByteChannel; import java.nio.charset.StandardCharsets; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; @@ -89,7 +87,7 @@ private void fix(Path pathToVault, VaultConfig config, Cryptor cryptor) throws I AtomicInteger dirCounter = new AtomicInteger(1); AtomicInteger symlinkCounter = new AtomicInteger(1); String longNameSuffix = createClearnameToBeShortened(config.getShorteningThreshold()); - Optional dirId = retrieveDirId(orphanedDir, cryptor); + Optional dirId = retrieveDirId(orphanedDir, cryptor); try (var orphanedContentStream = Files.newDirectoryStream(orphanedDir, this::matchesEncryptedContentPattern)) { for (Path orphanedResource : orphanedContentStream) { @@ -112,7 +110,7 @@ private void fix(Path pathToVault, VaultConfig config, Cryptor cryptor) throws I adoptOrphanedResource(orphanedResource, newClearName, isShortened, stepParentDir, cryptor.fileNameCryptor(), sha1); } } - Files.deleteIfExists(orphanedDir.resolve(Constants.DIR_BACKUP_FILE_NAME)); + Files.deleteIfExists(orphanedDir.resolve(Constants.DIR_ID_BACKUP_FILE_NAME)); try (var nonCryptomatorFiles = Files.newDirectoryStream(orphanedDir)) { for (Path p : nonCryptomatorFiles) { Files.move(p, stepParentDir.path().resolve(p.getFileName()), LinkOption.NOFOLLOW_LINKS); @@ -172,7 +170,7 @@ CiphertextDirectory prepareStepParent(Path dataDir, Path cipherRecoveryDir, Cryp var stepParentCipherDir = new CiphertextDirectory(stepParentUUID, stepParentDir); //only if it does not exist try { - DirectoryIdBackup.backupManually(cryptor, stepParentCipherDir); + DirectoryIdBackup.write(cryptor, stepParentCipherDir); } catch (FileAlreadyExistsException e) { // already exists due to a previous recovery attempt } @@ -180,29 +178,18 @@ CiphertextDirectory prepareStepParent(Path dataDir, Path cipherRecoveryDir, Cryp } //visible for testing - Optional retrieveDirId(Path orphanedDir, Cryptor cryptor) { - var dirIdFile = orphanedDir.resolve(Constants.DIR_BACKUP_FILE_NAME); - var dirIdBuffer = ByteBuffer.allocate(36); //a dir id contains at most 36 ascii chars - - try (var channel = Files.newByteChannel(dirIdFile, StandardOpenOption.READ); // - var decryptingChannel = createDecryptingReadableByteChannel(channel, cryptor)) { - ByteBuffers.fill(decryptingChannel, dirIdBuffer); - dirIdBuffer.flip(); - } catch (IOException e) { - LOG.info("Unable to read {}.", dirIdFile, e); + Optional retrieveDirId(Path orphanedDir, Cryptor cryptor) { + try { + byte[] dirId = DirectoryIdBackup.read(cryptor, orphanedDir); + return Optional.of(dirId); + } catch (IOException | CryptoException | IllegalStateException e) { + LOG.info("Unable to retrieve directory id for directory {}", orphanedDir, e); return Optional.empty(); } - - return Optional.of(StandardCharsets.US_ASCII.decode(dirIdBuffer).toString()); - } - - //exists and visible for testability - DecryptingReadableByteChannel createDecryptingReadableByteChannel(ByteChannel channel, Cryptor cryptor) { - return new DecryptingReadableByteChannel(channel, cryptor, true); } //visible for testing - String decryptFileName(Path orphanedResource, boolean isShortened, String dirId, FileNameCryptor cryptor) throws IOException, AuthenticationFailedException { + String decryptFileName(Path orphanedResource, boolean isShortened, byte [] dirId, FileNameCryptor cryptor) throws IOException, AuthenticationFailedException { final String filenameWithExtension; if (isShortened) { filenameWithExtension = Files.readString(orphanedResource.resolve(Constants.INFLATED_FILE_NAME)); @@ -211,7 +198,7 @@ String decryptFileName(Path orphanedResource, boolean isShortened, String dirId, } final String filename = filenameWithExtension.substring(0, filenameWithExtension.length() - Constants.CRYPTOMATOR_FILE_SUFFIX.length()); - return cryptor.decryptFilename(BaseEncoding.base64Url(), filename, dirId.getBytes(StandardCharsets.UTF_8)); + return cryptor.decryptFilename(BaseEncoding.base64Url(), filename, dirId); } // visible for testing diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java index 3b7d84621..43e9e2cef 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java @@ -1277,7 +1277,7 @@ public void createDirectoryBackupsDirIdInCiphertextDirPath() throws IOException inTest.createDirectory(path); verify(readonlyFlag).assertWritable(); - verify(dirIdBackup, Mockito.times(1)).execute(ciphertextDirectoryObject); + verify(dirIdBackup, Mockito.times(1)).write(ciphertextDirectoryObject); } diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java index a1dc036da..ab8f8beaf 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemProviderTest.java @@ -198,7 +198,7 @@ public void testInitialize() throws IOException, MasterkeyLoadingFailedException Optional dirIdBackup = Files.list(rootDir.get()).findFirst(); Assertions.assertTrue(dirIdBackup.isPresent()); Assertions.assertTrue(Files.isRegularFile(dirIdBackup.get())); - Assertions.assertEquals(Constants.DIR_BACKUP_FILE_NAME, dirIdBackup.get().getFileName().toString()); + Assertions.assertEquals(Constants.DIR_ID_BACKUP_FILE_NAME, dirIdBackup.get().getFileName().toString()); } @Test diff --git a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java index 12e090a24..713dc7f99 100644 --- a/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java +++ b/src/test/java/org/cryptomator/cryptofs/DeleteNonEmptyCiphertextDirectoryIntegrationTest.java @@ -169,7 +169,7 @@ private Path firstEmptyCiphertextDirectory() throws IOException { } private boolean isEmptyCryptoFsDirectory(Path path) { - Predicate isIgnoredFile = p -> Constants.DIR_BACKUP_FILE_NAME.equals(p.getFileName().toString()); + Predicate isIgnoredFile = p -> Constants.DIR_ID_BACKUP_FILE_NAME.equals(p.getFileName().toString()); try (Stream files = Files.list(path)) { return files.noneMatch(isIgnoredFile.negate()); } catch (IOException e) { @@ -181,7 +181,7 @@ private boolean isEmptyCryptoFsDirectory(Path path) { @DisplayName("Tests internal cryptofs directory emptiness definition") public void testCryptoFsDirEmptiness() throws IOException { var emptiness = pathToVault.getParent().resolve("emptiness"); - var ignoredFile = emptiness.resolve(Constants.DIR_BACKUP_FILE_NAME); + var ignoredFile = emptiness.resolve(Constants.DIR_ID_BACKUP_FILE_NAME); Files.createDirectory(emptiness); Files.createFile(ignoredFile); diff --git a/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java b/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java index 58739e909..cd0d16794 100644 --- a/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java +++ b/src/test/java/org/cryptomator/cryptofs/DirectoryIdBackupTest.java @@ -1,13 +1,16 @@ package org.cryptomator.cryptofs; import org.cryptomator.cryptofs.common.Constants; +import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.Cryptor; +import org.cryptomator.cryptolib.common.DecryptingReadableByteChannel; import org.cryptomator.cryptolib.common.EncryptingWritableByteChannel; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.mockito.MockedStatic; import org.mockito.Mockito; import java.io.IOException; @@ -15,6 +18,10 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; public class DirectoryIdBackupTest { @@ -22,8 +29,6 @@ public class DirectoryIdBackupTest { Path contentPath; private String dirId = "12345678"; - private CiphertextDirectory ciphertextDirectoryObject; - private EncryptingWritableByteChannel encChannel; private Cryptor cryptor; private DirectoryIdBackup dirIdBackup; @@ -31,37 +36,112 @@ public class DirectoryIdBackupTest { @BeforeEach public void init() { - ciphertextDirectoryObject = new CiphertextDirectory(dirId, contentPath); cryptor = Mockito.mock(Cryptor.class); - encChannel = Mockito.mock(EncryptingWritableByteChannel.class); - dirIdBackup = new DirectoryIdBackup(cryptor); } - @Test - public void testIdFileCreated() throws IOException { - try (MockedStatic backupMock = Mockito.mockStatic(DirectoryIdBackup.class)) { - backupMock.when(() -> DirectoryIdBackup.wrapEncryptionAround(Mockito.any(), Mockito.eq(cryptor))).thenReturn(encChannel); + @Nested + public class Write { + + private CiphertextDirectory ciphertextDirectoryObject; + private EncryptingWritableByteChannel encChannel; + + @BeforeEach + public void beforeEachWriteTest() { + ciphertextDirectoryObject = new CiphertextDirectory(dirId, contentPath); + encChannel = Mockito.mock(EncryptingWritableByteChannel.class); + } + + @Test + public void testIdFileCreated() throws IOException { + var dirIdBackupSpy = spy(dirIdBackup); + Mockito.doReturn(encChannel).when(dirIdBackupSpy).wrapEncryptionAround(Mockito.any(), Mockito.eq(cryptor)); Mockito.when(encChannel.write(Mockito.any())).thenReturn(0); - dirIdBackup.execute(ciphertextDirectoryObject); + dirIdBackupSpy.write(ciphertextDirectoryObject); - Assertions.assertTrue(Files.exists(contentPath.resolve(Constants.DIR_BACKUP_FILE_NAME))); + Assertions.assertTrue(Files.exists(contentPath.resolve(Constants.DIR_ID_BACKUP_FILE_NAME))); } + + @Test + public void testContentIsWritten() throws IOException { + var dirIdBackupSpy = spy(dirIdBackup); + Mockito.doReturn(encChannel).when(dirIdBackupSpy).wrapEncryptionAround(Mockito.any(), Mockito.eq(cryptor)); + Mockito.when(encChannel.write(Mockito.any())).thenReturn(0); + var expectedWrittenContent = ByteBuffer.wrap(dirId.getBytes(StandardCharsets.US_ASCII)); + + dirIdBackupSpy.write(ciphertextDirectoryObject); + + Mockito.verify(encChannel, Mockito.times(1)).write(Mockito.argThat(b -> b.equals(expectedWrittenContent))); + } + //TODO: test, what happens if file already exists? } - @Test - public void testContentIsWritten() throws IOException { - Mockito.when(encChannel.write(Mockito.any())).thenReturn(0); - var expectedWrittenContent = ByteBuffer.wrap(dirId.getBytes(StandardCharsets.US_ASCII)); + @Nested + public class Read { - try (MockedStatic backupMock = Mockito.mockStatic(DirectoryIdBackup.class)) { - backupMock.when(() -> DirectoryIdBackup.wrapEncryptionAround(Mockito.any(), Mockito.eq(cryptor))).thenReturn(encChannel); + private DecryptingReadableByteChannel decChannel; - dirIdBackup.execute(ciphertextDirectoryObject); + @BeforeEach + public void beforeEachRead() throws IOException { + var backupFile = contentPath.resolve(Constants.DIR_ID_BACKUP_FILE_NAME); + Files.writeString(backupFile, dirId, StandardCharsets.US_ASCII, StandardOpenOption.CREATE, StandardOpenOption.WRITE); - Mockito.verify(encChannel, Mockito.times(1)).write(Mockito.argThat(b -> b.equals(expectedWrittenContent))); + decChannel = mock(DecryptingReadableByteChannel.class); + } + + @Test + @DisplayName("If the directory id is longer than 36 characters, throw IllegalStateException") + public void contentLongerThan36Chars() throws IOException { + var dirIdBackupSpy = spy(dirIdBackup); + Mockito.when(dirIdBackupSpy.wrapDecryptionAround(Mockito.any(), Mockito.eq(cryptor))).thenReturn(decChannel); + Mockito.when(decChannel.read(Mockito.any())).thenReturn(Constants.MAX_DIR_ID_LENGTH + 1); + Assertions.assertThrows(IllegalStateException.class, () -> dirIdBackupSpy.read(contentPath)); + } + + @Test + @DisplayName("If the backup file cannot be decrypted, a CryptoException is thrown") + public void invalidEncryptionThrowsCryptoException() throws IOException { + var dirIdBackupSpy = spy(dirIdBackup); + var expectedException = new MyCryptoException(); + Mockito.when(dirIdBackupSpy.wrapDecryptionAround(Mockito.any(), Mockito.eq(cryptor))).thenReturn(decChannel); + Mockito.when(decChannel.read(Mockito.any())).thenThrow(expectedException); + var actual = Assertions.assertThrows(CryptoException.class, () -> dirIdBackupSpy.read(contentPath)); + Assertions.assertEquals(expectedException, actual); + } + + static class MyCryptoException extends CryptoException { + + } + + @Test + @DisplayName("IOException accessing the file is rethrown") + public void ioException() throws IOException { + var dirIdBackupSpy = spy(dirIdBackup); + var expectedException = new IOException("my oh my"); + Mockito.when(dirIdBackupSpy.wrapDecryptionAround(Mockito.any(), Mockito.eq(cryptor))).thenReturn(decChannel); + Mockito.when(decChannel.read(Mockito.any())).thenThrow(expectedException); + var actual = Assertions.assertThrows(IOException.class, () -> dirIdBackupSpy.read(contentPath)); + Assertions.assertEquals(expectedException, actual); + } + + @Test + @DisplayName("Valid dir id is read from the backup file") + public void success() throws IOException { + var dirIdBackupSpy = spy(dirIdBackup); + var expectedArray = dirId.getBytes(StandardCharsets.US_ASCII); + + Mockito.when(dirIdBackupSpy.wrapDecryptionAround(Mockito.any(), Mockito.eq(cryptor))).thenReturn(decChannel); + Mockito.doAnswer(invocationOnMock -> { + var buf = (ByteBuffer) invocationOnMock.getArgument(0); + buf.put(expectedArray); + return expectedArray.length; + }).when(decChannel).read(Mockito.any()); + + var readDirId = dirIdBackupSpy.read(contentPath); + Assertions.assertArrayEquals(expectedArray, readDirId); } } + } diff --git a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java index 10e9ba35f..240fe666d 100644 --- a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java +++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingContentDirTest.java @@ -55,13 +55,13 @@ public void testFix() throws IOException { var dirIdHash = "ridiculous-32-char-pseudo-hashhh"; Mockito.doReturn(dirIdHash).when(fileNameCryptor).hashDirectoryId(dirId); try (var dirIdBackupMock = Mockito.mockStatic(DirectoryIdBackup.class)) { - dirIdBackupMock.when(() -> DirectoryIdBackup.backupManually(Mockito.any(), Mockito.any())).thenAnswer(Answers.RETURNS_SMART_NULLS); + dirIdBackupMock.when(() -> DirectoryIdBackup.write(Mockito.any(), Mockito.any())).thenAnswer(Answers.RETURNS_SMART_NULLS); result.fix(pathToVault, cryptor); var expectedPath = pathToVault.resolve("d/ri/diculous-32-char-pseudo-hashhh"); ArgumentMatcher cipherDirMatcher = obj -> obj.dirId().equals(dirId) && obj.path().endsWith(expectedPath); - dirIdBackupMock.verify(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.argThat(cipherDirMatcher)), Mockito.times(1)); + dirIdBackupMock.verify(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.argThat(cipherDirMatcher)), Mockito.times(1)); var attr = Assertions.assertDoesNotThrow(() -> Files.readAttributes(expectedPath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)); Assertions.assertTrue(attr.isDirectory()); } @@ -73,7 +73,7 @@ public void testFixFailsOnFailingDirIdFile() throws IOException { var dirIdHash = "ridiculous-32-char-pseudo-hashhh"; try (var dirIdBackupMock = Mockito.mockStatic(DirectoryIdBackup.class)) { Mockito.doReturn(dirIdHash).when(fileNameCryptor).hashDirectoryId(dirId); - dirIdBackupMock.when(() -> DirectoryIdBackup.backupManually(Mockito.any(), Mockito.any())).thenThrow(new IOException("Access denied")); + dirIdBackupMock.when(() -> DirectoryIdBackup.write(Mockito.any(), Mockito.any())).thenThrow(new IOException("Access denied")); Assertions.assertThrows(IOException.class, () -> result.fix(pathToVault, cryptor)); } diff --git a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java index cb97d0b72..6d173b498 100644 --- a/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java +++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackupTest.java @@ -36,7 +36,7 @@ public void testFix() throws IOException { Path cipherDir = Path.of("d/ri/diculous-30-char-pseudo-hash"); String dirId = "1234-456789-1234"; try (var dirIdBackupMock = Mockito.mockStatic(DirectoryIdBackup.class)) { - dirIdBackupMock.when(() -> DirectoryIdBackup.backupManually(Mockito.any(), Mockito.any())).thenAnswer(Answers.RETURNS_SMART_NULLS); + dirIdBackupMock.when(() -> DirectoryIdBackup.write(Mockito.any(), Mockito.any())).thenAnswer(Answers.RETURNS_SMART_NULLS); Cryptor cryptor = Mockito.mock(Cryptor.class); result = new MissingDirIdBackup(dirId, cipherDir); @@ -44,7 +44,7 @@ public void testFix() throws IOException { var expectedPath = pathToVault.resolve(cipherDir); ArgumentMatcher cipherDirMatcher = obj -> obj.dirId().equals(dirId) && obj.path().isAbsolute() && obj.path().equals(expectedPath); - dirIdBackupMock.verify(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.argThat(cipherDirMatcher)), Mockito.times(1)); + dirIdBackupMock.verify(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.argThat(cipherDirMatcher)), Mockito.times(1)); } } } diff --git a/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java b/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDirTest.java similarity index 88% rename from src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java rename to src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDirTest.java index 9f9c93b27..48a4954b0 100644 --- a/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanDirTest.java +++ b/src/test/java/org/cryptomator/cryptofs/health/dirid/OrphanContentDirTest.java @@ -6,33 +6,33 @@ import org.cryptomator.cryptofs.VaultConfig; import org.cryptomator.cryptofs.common.Constants; import org.cryptomator.cryptolib.api.AuthenticationFailedException; +import org.cryptomator.cryptolib.api.CryptoException; import org.cryptomator.cryptolib.api.Cryptor; import org.cryptomator.cryptolib.api.FileNameCryptor; import org.cryptomator.cryptolib.api.Masterkey; -import org.cryptomator.cryptolib.common.DecryptingReadableByteChannel; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.FieldSource; import org.mockito.Mockito; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.security.MessageDigest; +import java.util.List; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -public class OrphanDirTest { +public class OrphanContentDirTest { @TempDir public Path pathToVault; @@ -162,11 +162,11 @@ public void testPrepareStepParent() throws IOException { UUID uuid = Mockito.mock(UUID.class); uuidClass.when(UUID::randomUUID).thenReturn(uuid); Mockito.doReturn("aaaaaa").when(uuid).toString(); - dirIdBackupClass.when(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.any())).thenAnswer(invocation -> null); + dirIdBackupClass.when(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.any())).thenAnswer(invocation -> null); result.prepareStepParent(dataDir, cipherRecovery, cryptor, clearStepParentName); - dirIdBackupClass.verify(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.any()), Mockito.times(1)); + dirIdBackupClass.verify(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.any()), Mockito.times(1)); } Assertions.assertEquals("aaaaaa", Files.readString(cipherRecovery.resolve("2.c9r/dir.c9r"), StandardCharsets.UTF_8)); Assertions.assertTrue(Files.isDirectory(pathToVault.resolve("d/22/2222"))); @@ -186,11 +186,11 @@ public void testPrepareStepParentExistingStepParentDir() throws IOException { UUID uuid = Mockito.mock(UUID.class); uuidClass.when(UUID::randomUUID).thenReturn(uuid); Mockito.doReturn("aaaaaa").when(uuid).toString(); - dirIdBackupClass.when(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.any())).thenThrow(new FileAlreadyExistsException("dirId file exists")); + dirIdBackupClass.when(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.any())).thenThrow(new FileAlreadyExistsException("dirId file exists")); result.prepareStepParent(dataDir, cipherRecovery, cryptor, clearStepParentName); - dirIdBackupClass.verify(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.any()), Mockito.times(1)); + dirIdBackupClass.verify(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.any()), Mockito.times(1)); } Assertions.assertEquals("aaaaaa", Files.readString(cipherRecovery.resolve("2.c9r/dir.c9r"), StandardCharsets.UTF_8)); Assertions.assertTrue(Files.isDirectory(pathToVault.resolve("d/22/2222"))); @@ -210,11 +210,11 @@ public void testPrepareStepParentOrphanedStepParentDir() throws IOException { UUID uuid = Mockito.mock(UUID.class); uuidClass.when(UUID::randomUUID).thenReturn(uuid); Mockito.doReturn("aaaaaa").when(uuid).toString(); - dirIdBackupClass.when(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.any())).thenAnswer(invocation -> null); + dirIdBackupClass.when(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.any())).thenAnswer(invocation -> null); result.prepareStepParent(dataDir, cipherRecovery, cryptor, clearStepParentName); - dirIdBackupClass.verify(() -> DirectoryIdBackup.backupManually(Mockito.eq(cryptor), Mockito.any()), Mockito.times(1)); + dirIdBackupClass.verify(() -> DirectoryIdBackup.write(Mockito.eq(cryptor), Mockito.any()), Mockito.times(1)); } Assertions.assertEquals("aaaaaa", Files.readString(cipherRecovery.resolve("2.c9r/dir.c9r"), StandardCharsets.UTF_8)); Assertions.assertTrue(Files.isDirectory(pathToVault.resolve("d/22/2222"))); @@ -227,46 +227,38 @@ class RetrieveDirIdTests { private OrphanContentDir resultSpy; + static class MyCryptoException extends CryptoException { + + } + + static List expectedExceptions = List.of(new IOException(), new IllegalStateException(), new MyCryptoException()); + @BeforeEach public void init() { resultSpy = Mockito.spy(result); } @Test - @DisplayName("retrieveDirId extracts directory id of cipher-dir/dirId.c9r") - public void testRetrieveDirIdSuccess() throws IOException { - var dirIdFile = cipherOrphan.resolve(Constants.DIR_BACKUP_FILE_NAME); - var dirId = "random-uuid-with-at-most-36chars"; - - Files.writeString(dirIdFile, dirId, StandardCharsets.US_ASCII, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); - DecryptingReadableByteChannel dirIdReadChannel = Mockito.mock(DecryptingReadableByteChannel.class); - - Mockito.doReturn(dirIdReadChannel).when(resultSpy).createDecryptingReadableByteChannel(Mockito.any(), Mockito.eq(cryptor)); - AtomicInteger readBytesInMockedChannel = new AtomicInteger(0); - //in every invocation the channel position is updated, simulating a stateful channel - Mockito.doAnswer(invocationOnMock -> { - ByteBuffer buf = invocationOnMock.getArgument(0); - try (SeekableByteChannel channel = Files.newByteChannel(dirIdFile, StandardOpenOption.READ)) { - channel.position(readBytesInMockedChannel.get()); - readBytesInMockedChannel.getAndSet(channel.read(buf)); - return readBytesInMockedChannel.get(); - } - }).when(dirIdReadChannel).read(Mockito.any()); - - Mockito.when(fileNameCryptor.hashDirectoryId(dirId)).thenReturn("333333"); - - var maybeDirId = resultSpy.retrieveDirId(cipherOrphan, cryptor); - - Assertions.assertTrue(maybeDirId.isPresent()); - Assertions.assertEquals(dirId, maybeDirId.get()); + @DisplayName("Successful reading dirId from backup file") + public void success() { + var dirId = new byte[]{'f', 'o', 'o'}; + try (var dirIdBackupMock = Mockito.mockStatic(DirectoryIdBackup.class)) { + dirIdBackupMock.when(() -> DirectoryIdBackup.read(cryptor, cipherOrphan)).thenReturn(dirId); + var result = resultSpy.retrieveDirId(cipherOrphan, cryptor); + Assertions.assertTrue(result.isPresent()); + Assertions.assertArrayEquals(dirId, result.get()); + } } - @Test - @DisplayName("retrieveDirId returns an empty optional if cipher-dir/dirId.c9r cannot be read") - public void testRetrieveDirIdIOExceptionReadingFile() throws IOException { - var notExistingResult = resultSpy.retrieveDirId(cipherOrphan, cryptor); - - Assertions.assertTrue(notExistingResult.isEmpty()); + @ParameterizedTest + @DisplayName("retrieveDirId returns an empty optional on any exception") + @FieldSource("expectedExceptions") + public void testRetrieveDirIdIOExceptionReadingFile(Throwable t) throws IOException { + try (var dirIdBackupMock = Mockito.mockStatic(DirectoryIdBackup.class)) { + dirIdBackupMock.when(() -> DirectoryIdBackup.read(cryptor, cipherOrphan)).thenThrow(t); + var notExistingResult = resultSpy.retrieveDirId(cipherOrphan, cryptor); + Assertions.assertTrue(notExistingResult.isEmpty()); + } } } @@ -283,7 +275,7 @@ void testRestoreFilenameNormalSuccess() throws IOException { //by using Mockito.eq() in filename parameter Mockito.verfiy() not necessary Mockito.when(fileNameCryptor.decryptFilename(Mockito.any(), Mockito.eq("orphan"), Mockito.any())).thenReturn("theTrueName.txt"); - String decryptedFile = result.decryptFileName(oldCipherPath, false, "someDirId", fileNameCryptor); + String decryptedFile = result.decryptFileName(oldCipherPath, false, new byte[]{}, fileNameCryptor); Assertions.assertEquals("theTrueName.txt", decryptedFile); } @@ -299,7 +291,7 @@ void testRestoreFilenameShortenedSuccess() throws IOException { //by using Mockito.eq() in filename parameter Mockito.verfiy() not necessary Mockito.when(fileNameCryptor.decryptFilename(Mockito.any(), Mockito.eq("OrphanWithLongestName"), Mockito.any())).thenReturn("theRealLongName.txt"); - String decryptedFile = result.decryptFileName(oldCipherPath, true, "someDirId", fileNameCryptor); + String decryptedFile = result.decryptFileName(oldCipherPath, true, new byte[]{}, fileNameCryptor); Assertions.assertEquals("theRealLongName.txt", decryptedFile); } @@ -310,7 +302,7 @@ void testRestoreFilenameShortenedIOException() throws IOException { Path oldCipherPath = cipherOrphan.resolve("hashOfOrphanWithLongestName.c9r"); Files.createDirectory(oldCipherPath); - Assertions.assertThrows(IOException.class, () -> result.decryptFileName(oldCipherPath, true, "someDirId", fileNameCryptor)); + Assertions.assertThrows(IOException.class, () -> result.decryptFileName(oldCipherPath, true, new byte[]{}, fileNameCryptor)); } } @@ -438,9 +430,9 @@ public void testFixContinuesOnNotRecoverableFilename() throws IOException { Path orphan2 = cipherOrphan.resolve("orphan2_with_at_least_26chars.c9s"); Files.createFile(orphan1); Files.createDirectories(orphan2); - Files.createFile(cipherOrphan.resolve(Constants.DIR_BACKUP_FILE_NAME)); + Files.createFile(cipherOrphan.resolve(Constants.DIR_ID_BACKUP_FILE_NAME)); - var dirId = Optional.of("trololo-id"); + var dirId = Optional.of(new byte[]{'t', 'r', 'o', 'l', 'o', 'l', 'o'}); CiphertextDirectory stepParentDir = new CiphertextDirectory("aaaaaa", dataDir.resolve("22/2222")); @@ -477,9 +469,9 @@ public void testFixWithDirId() throws IOException { Path orphan2 = cipherOrphan.resolve("orphan2_with_at_least_26chars.c9s"); Files.createFile(orphan1); Files.createDirectories(orphan2); - Files.createFile(cipherOrphan.resolve(Constants.DIR_BACKUP_FILE_NAME)); + Files.createFile(cipherOrphan.resolve(Constants.DIR_ID_BACKUP_FILE_NAME)); - var dirId = Optional.of("trololo-id"); + var dirId = Optional.of(new byte[]{'t', 'r', 'o', 'l', 'o', 'l', 'o'}); CiphertextDirectory stepParentDir = new CiphertextDirectory("aaaaaa", dataDir.resolve("22/2222")); @@ -500,7 +492,7 @@ public void testFixWithDirId() throws IOException { resultSpy.fix(pathToVault, config, masterkey, cryptor); - Mockito.verify(resultSpy, Mockito.never()).adoptOrphanedResource(Mockito.eq(cipherOrphan.resolve(Constants.DIR_BACKUP_FILE_NAME)), Mockito.any(), Mockito.anyBoolean(), Mockito.eq(stepParentDir), Mockito.eq(fileNameCryptor), Mockito.any()); + Mockito.verify(resultSpy, Mockito.never()).adoptOrphanedResource(Mockito.eq(cipherOrphan.resolve(Constants.DIR_ID_BACKUP_FILE_NAME)), Mockito.any(), Mockito.anyBoolean(), Mockito.eq(stepParentDir), Mockito.eq(fileNameCryptor), Mockito.any()); Mockito.verify(resultSpy, Mockito.times(1)).adoptOrphanedResource(Mockito.eq(orphan1), Mockito.eq(lostName1), Mockito.anyBoolean(), Mockito.eq(stepParentDir), Mockito.eq(fileNameCryptor), Mockito.any()); Mockito.verify(resultSpy, Mockito.times(1)).adoptOrphanedResource(Mockito.eq(orphan2), Mockito.eq(lostName2), Mockito.anyBoolean(), Mockito.eq(stepParentDir), Mockito.eq(fileNameCryptor), Mockito.any()); Assertions.assertTrue(Files.notExists(cipherOrphan)); @@ -520,9 +512,9 @@ public void testFixWithNonCryptomatorFiles() throws IOException { Files.createFile(orphan1); Files.createDirectories(orphan2); Files.createFile(unrelated); - Files.createFile(cipherOrphan.resolve(Constants.DIR_BACKUP_FILE_NAME)); + Files.createFile(cipherOrphan.resolve(Constants.DIR_ID_BACKUP_FILE_NAME)); - var dirId = Optional.of("trololo-id"); + var dirId = Optional.of(new byte[]{'t', 'r', 'o', 'l', 'o', 'l', 'o'}); CiphertextDirectory stepParentDir = new CiphertextDirectory("aaaaaa", dataDir.resolve("22/2222")); Files.createDirectories(stepParentDir.path()); //needs to be created here, otherwise the Files.move(non-crypto-resource, stepparent) will fail @@ -605,5 +597,4 @@ public void testFixOrphanedRecoveryDir() throws IOException { Mockito.verify(resultSpy, Mockito.never()).adoptOrphanedResource(Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.eq(fileNameCryptor), Mockito.any()); Mockito.verify(resultSpy).prepareRecoveryDir(pathToVault, fileNameCryptor); } - }