diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d26ab7d2..da527ddf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: show-progress: false - uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: 'temurin' cache: 'maven' - name: Cache SonarCloud packages diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 237bcac3..9bfa8088 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -22,7 +22,7 @@ jobs: show-progress: false - uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: 'temurin' cache: 'maven' - name: Initialize CodeQL diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml index 4e7e7d0f..601975f4 100644 --- a/.github/workflows/dependency-check.yml +++ b/.github/workflows/dependency-check.yml @@ -14,7 +14,7 @@ jobs: with: runner-os: 'ubuntu-latest' java-distribution: 'temurin' - java-version: 17 + java-version: 21 secrets: nvd-api-key: ${{ secrets.NVD_API_KEY }} slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/publish-central.yml b/.github/workflows/publish-central.yml index 4129b18c..07727e4c 100644 --- a/.github/workflows/publish-central.yml +++ b/.github/workflows/publish-central.yml @@ -16,7 +16,7 @@ jobs: show-progress: false - uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: 'temurin' cache: 'maven' server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml diff --git a/.github/workflows/publish-github.yml b/.github/workflows/publish-github.yml index dff71ad5..59b312e4 100644 --- a/.github/workflows/publish-github.yml +++ b/.github/workflows/publish-github.yml @@ -12,7 +12,7 @@ jobs: show-progress: false - uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: 'temurin' cache: 'maven' - name: Enforce project version ${{ github.event.release.tag_name }} diff --git a/.idea/misc.xml b/.idea/misc.xml index 67e1e611..9dc782bb 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -8,5 +8,5 @@ - + \ No newline at end of file diff --git a/README.md b/README.md index 6ed04e88..3050a0db 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ [![Known Vulnerabilities](https://snyk.io/test/github/cryptomator/cryptofs/badge.svg)](https://snyk.io/test/github/cryptomator/cryptofs) **CryptoFS:** Implementation of the [Cryptomator](https://github.com/cryptomator/cryptomator) encryption scheme. +For more info about the encryption scheme, read the [docs](https://docs.cryptomator.org/en/latest/security/vault/). ## Features @@ -98,7 +99,7 @@ For more details on how to use the constructed `FileSystem`, you may consult the ### Dependencies -* Java 17 +* Java 21 * Maven 3 ### Run Maven diff --git a/pom.xml b/pom.xml index 3847f6b0..569edd68 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ UTF-8 - 17 + 21 2.2.0 @@ -27,7 +27,7 @@ 5.10.3 - 5.2.0 + 5.12.0 3.0 1.3.0 @@ -114,7 +114,7 @@ org.mockito - mockito-inline + mockito-core ${mockito.version} test diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java index e694130f..5095b5cd 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoFileSystemImplTest.java @@ -72,7 +72,6 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; @@ -959,6 +958,7 @@ public void copyDirectory() throws IOException { when(physicalFsProv.newFileChannel(Mockito.eq(ciphertextDestinationDirFile), Mockito.any(), any(FileAttribute[].class))).thenReturn(ciphertextTargetDirDirFileFileChannel); when(cryptoPathMapper.getCiphertextFileType(cleartextSource)).thenReturn(CiphertextFileType.DIRECTORY); when(cryptoPathMapper.getCiphertextFileType(cleartextDestination)).thenThrow(NoSuchFileException.class); + when(physicalFsProv.exists(ciphertextTargetParent)).thenReturn(true); Mockito.doThrow(new NoSuchFileException("ciphertextDestinationDirFile")).when(physicalFsProv).checkAccess(ciphertextDestinationFile); inTest.copy(cleartextSource, cleartextDestination); @@ -1007,6 +1007,7 @@ public void moveDirectoryCopyBasicAttributes() throws IOException { when(physicalFsProv.readAttributes(Mockito.same(ciphertextSourceDir), Mockito.same(BasicFileAttributes.class), any(LinkOption[].class))).thenReturn(srcAttrs); when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextDestinationDir), Mockito.same(BasicFileAttributeView.class), any(LinkOption[].class))).thenReturn(dstAttrView); when(physicalFsProv.newFileChannel(Mockito.same(ciphertextDestinationDirFile), Mockito.anySet(), any(FileAttribute[].class))).thenReturn(ciphertextTargetDirDirFileFileChannel); + when(physicalFsProv.exists(ciphertextTargetParent)).thenReturn(true); inTest.copy(cleartextSource, cleartextDestination, StandardCopyOption.COPY_ATTRIBUTES); @@ -1027,6 +1028,7 @@ public void moveDirectoryCopyFileOwnerAttributes() throws IOException { when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextSourceDir), Mockito.same(FileOwnerAttributeView.class), any(LinkOption[].class))).thenReturn(srcAttrsView); when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextDestinationDir), Mockito.same(FileOwnerAttributeView.class), any(LinkOption[].class))).thenReturn(dstAttrView); when(physicalFsProv.newFileChannel(Mockito.same(ciphertextDestinationDirFile), Mockito.anySet(), any(FileAttribute[].class))).thenReturn(ciphertextTargetDirDirFileFileChannel); + when(physicalFsProv.exists(ciphertextTargetParent)).thenReturn(true); inTest.copy(cleartextSource, cleartextDestination, StandardCopyOption.COPY_ATTRIBUTES); @@ -1050,6 +1052,7 @@ public void moveDirectoryCopyPosixAttributes() throws IOException { when(physicalFsProv.readAttributes(Mockito.same(ciphertextSourceDir), Mockito.same(PosixFileAttributes.class), any(LinkOption[].class))).thenReturn(srcAttrs); when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextDestinationDir), Mockito.same(PosixFileAttributeView.class), any(LinkOption[].class))).thenReturn(dstAttrView); when(physicalFsProv.newFileChannel(Mockito.same(ciphertextDestinationDirFile), Mockito.anySet(), any(FileAttribute[].class))).thenReturn(ciphertextTargetDirDirFileFileChannel); + when(physicalFsProv.exists(ciphertextTargetParent)).thenReturn(true); inTest.copy(cleartextSource, cleartextDestination, StandardCopyOption.COPY_ATTRIBUTES); @@ -1073,6 +1076,7 @@ public void moveDirectoryCopyDosAttributes() throws IOException { when(physicalFsProv.readAttributes(Mockito.same(ciphertextSourceDir), Mockito.same(DosFileAttributes.class), any(LinkOption[].class))).thenReturn(srcAttrs); when(physicalFsProv.getFileAttributeView(Mockito.same(ciphertextDestinationDir), Mockito.same(DosFileAttributeView.class), any(LinkOption[].class))).thenReturn(dstAttrView); when(physicalFsProv.newFileChannel(Mockito.same(ciphertextDestinationDirFile), Mockito.anySet(), any(FileAttribute[].class))).thenReturn(ciphertextTargetDirDirFileFileChannel); + when(physicalFsProv.exists(ciphertextTargetParent)).thenReturn(true); inTest.copy(cleartextSource, cleartextDestination, StandardCopyOption.COPY_ATTRIBUTES); @@ -1168,6 +1172,7 @@ public void createDirectoryIfPathCiphertextFileDoesExistThrowsFileAlreadyExcepti when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("foo", ciphertextParent)); when(ciphertextParent.getFileSystem()).thenReturn(fileSystem); doThrow(new FileAlreadyExistsException(path.toString())).when(cryptoPathMapper).assertNonExisting(path); + when(provider.exists(ciphertextParent)).thenReturn(true); FileAlreadyExistsException e = Assertions.assertThrows(FileAlreadyExistsException.class, () -> { inTest.createDirectory(path); @@ -1177,7 +1182,7 @@ public void createDirectoryIfPathCiphertextFileDoesExistThrowsFileAlreadyExcepti @Test public void createDirectoryCreatesDirectoryIfConditonsAreMet() throws IOException { - Path ciphertextParent = mock(Path.class, "ciphertextParent"); + Path ciphertextParent = mock(Path.class, "d/00/00"); Path ciphertextRawPath = mock(Path.class, "d/00/00/path.c9r"); Path ciphertextDirFile = mock(Path.class, "d/00/00/path.c9r/dir.c9r"); Path ciphertextDirPath = mock(Path.class, "d/FF/FF/"); @@ -1187,7 +1192,7 @@ public void createDirectoryCreatesDirectoryIfConditonsAreMet() throws IOExceptio when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile); when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath); when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, ciphertextDirPath)); - when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextParent)); when(cryptoPathMapper.getCiphertextFileType(path)).thenThrow(NoSuchFileException.class); when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath); when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFile); @@ -1197,6 +1202,7 @@ public void createDirectoryCreatesDirectoryIfConditonsAreMet() throws IOExceptio when(ciphertextDirPath.getFileSystem()).thenReturn(fileSystem); when(ciphertextDirFile.getName(3)).thenReturn(mock(Path.class, "path.c9r")); when(provider.newFileChannel(ciphertextDirFile, EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE))).thenReturn(channel); + when(provider.exists(ciphertextParent)).thenReturn(true); inTest.createDirectory(path); @@ -1206,7 +1212,7 @@ public void createDirectoryCreatesDirectoryIfConditonsAreMet() throws IOExceptio @Test public void createDirectoryClearsDirIdAndDeletesDirFileIfCreatingDirFails() throws IOException { - Path ciphertextParent = mock(Path.class, "ciphertextParent"); + Path ciphertextParent = mock(Path.class, "d/00/00"); Path ciphertextRawPath = mock(Path.class, "d/00/00/path.c9r"); Path ciphertextDirFile = mock(Path.class, "d/00/00/path.c9r/dir.c9r"); Path ciphertextDirPath = mock(Path.class, "d/FF/FF/"); @@ -1216,7 +1222,7 @@ public void createDirectoryClearsDirIdAndDeletesDirFileIfCreatingDirFails() thro when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile); when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath); when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(new CiphertextDirectory(dirId, ciphertextDirPath)); - when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextParent)); when(cryptoPathMapper.getCiphertextFileType(path)).thenThrow(NoSuchFileException.class); when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath); when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFile); @@ -1226,15 +1232,18 @@ public void createDirectoryClearsDirIdAndDeletesDirFileIfCreatingDirFails() thro when(ciphertextDirPath.getFileSystem()).thenReturn(fileSystem); when(ciphertextDirFile.getName(3)).thenReturn(mock(Path.class, "path.c9r")); when(provider.newFileChannel(ciphertextDirFile, EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE))).thenReturn(channel); + when(provider.exists(ciphertextParent)).thenReturn(true); - // make createDirectory with an FileSystemException during Files.createDirectories(ciphertextDirPath) - doThrow(new IOException()).when(provider).createDirectory(ciphertextDirPath); + // make createDirectory with an FileSystemException during Files.createDirectories(ciphertextContentDir) + doThrow(new IOException()).when(provider).readAttributesIfExists(ciphertextDirPath, BasicFileAttributes.class); + doThrow(new FileAlreadyExistsException("very specific")).when(provider).createDirectory(ciphertextDirPath); when(ciphertextDirPath.toAbsolutePath()).thenReturn(ciphertextDirPath); when(ciphertextDirPath.getParent()).thenReturn(null); - Assertions.assertThrows(IOException.class, () -> { + var exception = Assertions.assertThrows(FileAlreadyExistsException.class, () -> { inTest.createDirectory(path); }); + Assertions.assertEquals("very specific", exception.getMessage()); verify(readonlyFlag).assertWritable(); verify(provider).delete(ciphertextDirFile); verify(dirIdProvider).delete(ciphertextDirFile); @@ -1243,7 +1252,7 @@ public void createDirectoryClearsDirIdAndDeletesDirFileIfCreatingDirFails() thro @Test public void createDirectoryBackupsDirIdInCiphertextDirPath() throws IOException { - Path ciphertextParent = mock(Path.class, "ciphertextParent"); + Path ciphertextParent = mock(Path.class, "d/00/00"); Path ciphertextRawPath = mock(Path.class, "d/00/00/path.c9r"); Path ciphertextDirFile = mock(Path.class, "d/00/00/path.c9r/dir.c9r"); Path ciphertextDirPath = mock(Path.class, "d/FF/FF/"); @@ -1254,7 +1263,7 @@ public void createDirectoryBackupsDirIdInCiphertextDirPath() throws IOException when(ciphertextRawPath.resolve("dir.c9r")).thenReturn(ciphertextDirFile); when(cryptoPathMapper.getCiphertextFilePath(path)).thenReturn(ciphertextPath); when(cryptoPathMapper.getCiphertextDir(path)).thenReturn(cipherDirObject); - when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextDirPath)); + when(cryptoPathMapper.getCiphertextDir(parent)).thenReturn(new CiphertextDirectory("parentDirId", ciphertextParent)); when(cryptoPathMapper.getCiphertextFileType(path)).thenThrow(NoSuchFileException.class); when(ciphertextPath.getRawPath()).thenReturn(ciphertextRawPath); when(ciphertextPath.getDirFilePath()).thenReturn(ciphertextDirFile); @@ -1264,6 +1273,7 @@ public void createDirectoryBackupsDirIdInCiphertextDirPath() throws IOException when(ciphertextDirPath.getFileSystem()).thenReturn(fileSystem); when(ciphertextDirFile.getName(3)).thenReturn(mock(Path.class, "path.c9r")); when(provider.newFileChannel(ciphertextDirFile, EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE))).thenReturn(channel); + when(provider.exists(ciphertextParent)).thenReturn(true); inTest.createDirectory(path); diff --git a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java index e2da36a8..32241fb8 100644 --- a/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java +++ b/src/test/java/org/cryptomator/cryptofs/CryptoPathMapperTest.java @@ -290,6 +290,7 @@ public void testGetCiphertextFileTypeForDirectory() throws IOException { Mockito.when(underlyingFileSystemProvider.readAttributes(dirFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(Mockito.mock(BasicFileAttributes.class)); Mockito.when(underlyingFileSystemProvider.readAttributes(symlinkFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class); Mockito.when(underlyingFileSystemProvider.readAttributes(contentsFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class); + Mockito.when(underlyingFileSystemProvider.exists(dirFilePath, LinkOption.NOFOLLOW_LINKS)).thenReturn(true); CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); @@ -305,6 +306,7 @@ public void testGetCiphertextFileTypeForSymlink() throws IOException { Mockito.when(underlyingFileSystemProvider.readAttributes(dirFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class); Mockito.when(underlyingFileSystemProvider.readAttributes(symlinkFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(Mockito.mock(BasicFileAttributes.class)); Mockito.when(underlyingFileSystemProvider.readAttributes(contentsFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenThrow(NoSuchFileException.class); + Mockito.when(underlyingFileSystemProvider.exists(symlinkFilePath, LinkOption.NOFOLLOW_LINKS)).thenReturn(true); CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig); @@ -322,6 +324,7 @@ public void testGetCiphertextFileTypeForShortenedFile() throws IOException { Mockito.when(underlyingFileSystemProvider.readAttributes(contentsFilePath, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS)).thenReturn(Mockito.mock(BasicFileAttributes.class)); Mockito.when(fileNameCryptor.encryptFilename(Mockito.any(), Mockito.eq("LONGCLEAR"), Mockito.any())).thenReturn(Strings.repeat("A", 1000)); Mockito.when(longFileNameProvider.deflate(Mockito.any())).thenReturn(new LongFileNameProvider.DeflatedFileName(c9rPath, null, null)); + Mockito.when(underlyingFileSystemProvider.exists(contentsFilePath, LinkOption.NOFOLLOW_LINKS)).thenReturn(true); CryptoPathMapper mapper = new CryptoPathMapper(pathToVault, cryptor, dirIdProvider, longFileNameProvider, vaultConfig);