Skip to content

Commit

Permalink
Merge branch 'release/2.4.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
infeo committed Mar 30, 2022
2 parents 0def366 + d8d8c56 commit d9c8bef
Show file tree
Hide file tree
Showing 35 changed files with 542 additions and 587 deletions.
6 changes: 0 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- name: Upload code coverage report
id: codacyCoverageReporter
run: bash <(curl -Ls https://coverage.codacy.com/get.sh)
env:
CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }}
continue-on-error: true
- uses: actions/upload-artifact@v2
with:
name: artifacts
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
![cryptomator](cryptomator.png)

[![Build](https://github.com/cryptomator/cryptofs/workflows/Build/badge.svg)](https://github.com/cryptomator/cryptofs/actions?query=workflow%3ABuild)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/7248ca7d466843f785f79f33374302c2)](https://www.codacy.com/gh/cryptomator/cryptofs/dashboard)
[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/7248ca7d466843f785f79f33374302c2)](https://www.codacy.com/gh/cryptomator/cryptofs/dashboard)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=cryptomator_cryptofs&metric=alert_status)](https://sonarcloud.io/dashboard?id=cryptomator_cryptofs)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=cryptomator_cryptofs&metric=coverage)](https://sonarcloud.io/dashboard?id=cryptomator_cryptofs)
[![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.
Expand Down
34 changes: 16 additions & 18 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptofs</artifactId>
<version>2.3.1</version>
<version>2.4.0</version>
<name>Cryptomator Crypto Filesystem</name>
<description>This library provides the Java filesystem provider used by Cryptomator.</description>
<url>https://github.com/cryptomator/cryptofs</url>
Expand All @@ -19,23 +19,20 @@

<!-- dependencies -->
<cryptolib.version>2.0.3</cryptolib.version>
<jwt.version>3.18.1</jwt.version>
<dagger.version>2.37</dagger.version>
<guava.version>30.1.1-jre</guava.version>
<slf4j.version>1.7.31</slf4j.version>
<jwt.version>3.19.1</jwt.version>
<dagger.version>2.41</dagger.version>
<guava.version>31.1-jre</guava.version>
<slf4j.version>1.7.36</slf4j.version>

<!-- test dependencies -->
<junit.jupiter.version>5.7.2</junit.jupiter.version>
<mockito.version>3.11.2</mockito.version>
<junit.jupiter.version>5.8.2</junit.jupiter.version>
<mockito.version>4.4.0</mockito.version>
<hamcrest.version>2.2</hamcrest.version>

<!-- build plugin dependencies -->
<dependency-check.version>6.2.2</dependency-check.version>
<dependency-check.version>7.0.3</dependency-check.version>
<jacoco.version>0.8.7</jacoco.version>
<nexus-staging.version>1.6.8</nexus-staging.version>

<!-- empty default needed to not break testing due to maven-surefire-plugin config -->
<argLine></argLine>
<nexus-staging.version>1.6.12</nexus-staging.version>
</properties>

<licenses>
Expand Down Expand Up @@ -118,7 +115,7 @@
<dependency>
<groupId>com.google.jimfs</groupId>
<artifactId>jimfs</artifactId>
<version>1.1</version>
<version>1.2</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand All @@ -145,11 +142,12 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<!-- Allow reflection for Mockito, so it can properly mock non-public classes -->
<argLine>${argLine} --add-opens=org.cryptomator.cryptofs/org.cryptomator.cryptofs.health.dirid=ALL-UNNAMED
--add-opens=org.cryptomator.cryptofs/org.cryptomator.cryptofs.health.type=ALL-UNNAMED
--add-opens=org.cryptomator.cryptofs/org.cryptomator.cryptofs.health.shortened=ALL-UNNAMED</argLine>
</configuration>
<systemPropertyVariables>
<org.slf4j.simpleLogger.defaultLogLevel>ERROR</org.slf4j.simpleLogger.defaultLogLevel>
</systemPropertyVariables>
<!-- JPMS blocks Mockito from deep reflection unless opening the module... -->
<useModulePath>false</useModulePath>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.cryptomator.cryptofs.common.Constants;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;
Expand Down Expand Up @@ -59,7 +60,9 @@ public String toString() {
return path.toString();
}

public void persistLongFileName() {
deflatedFileName.ifPresent(LongFileNameProvider.DeflatedFileName::persist);
public void persistLongFileName() throws IOException {
if( deflatedFileName.isPresent()) {
deflatedFileName.get().persist();
}
}
}
22 changes: 15 additions & 7 deletions src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -353,15 +353,20 @@ private FileChannel newFileChannelFromFile(CryptoPath cleartextFilePath, Effecti
}
CiphertextFilePath ciphertextPath = cryptoPathMapper.getCiphertextFilePath(cleartextFilePath);
Path ciphertextFilePath = ciphertextPath.getFilePath();

if (options.createNew() && openCryptoFiles.get(ciphertextFilePath).isPresent()) {
throw new FileAlreadyExistsException(cleartextFilePath.toString());
} else {
if (ciphertextPath.isShortened() && options.createNew()) {
Files.createDirectory(ciphertextPath.getRawPath()); // might throw FileAlreadyExists
} else if (ciphertextPath.isShortened()) {
Files.createDirectories(ciphertextPath.getRawPath()); // suppresses FileAlreadyExists
}
FileChannel ch = openCryptoFiles.getOrCreate(ciphertextFilePath).newFileChannel(options); // might throw FileAlreadyExists
}

//handle shortened files
if (ciphertextPath.isShortened() && options.createNew()) {
Files.createDirectory(ciphertextPath.getRawPath()); // might throw FileAlreadyExists
} else if (ciphertextPath.isShortened()) {
Files.createDirectories(ciphertextPath.getRawPath()); // suppresses FileAlreadyExists
}

FileChannel ch = openCryptoFiles.getOrCreate(ciphertextFilePath).newFileChannel(options); // might throw FileAlreadyExists
try {
if (options.writable()) {
ciphertextPath.persistLongFileName();
stats.incrementAccessesWritten();
Expand All @@ -370,6 +375,9 @@ private FileChannel newFileChannelFromFile(CryptoPath cleartextFilePath, Effecti
stats.incrementAccessesRead();
}
return ch;
} catch (Exception e){
ch.close();
throw e;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,9 @@ public static class DeflatedFileName {
this.readonlyFlag = readonlyFlag;
}

public void persist() {
public void persist() throws IOException {
readonlyFlag.assertWritable();
try {
persistInternal();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

private void persistInternal() throws IOException {
Path longNameFile = c9sPath.resolve(INFLATED_FILE_NAME);
Files.createDirectories(c9sPath);
Files.writeString(longNameFile, longName, UTF_8, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
Expand Down
31 changes: 20 additions & 11 deletions src/main/java/org/cryptomator/cryptofs/ch/CleartextFileChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
import com.google.common.base.Preconditions;
import org.cryptomator.cryptofs.CryptoFileSystemStats;
import org.cryptomator.cryptofs.EffectiveOpenOptions;
import org.cryptomator.cryptofs.fh.BufferPool;
import org.cryptomator.cryptofs.fh.ByteSource;
import org.cryptomator.cryptofs.fh.ChunkCache;
import org.cryptomator.cryptofs.fh.ChunkData;
import org.cryptomator.cryptofs.fh.Chunk;
import org.cryptomator.cryptofs.fh.ExceptionsDuringWrite;
import org.cryptomator.cryptofs.fh.OpenFileModifiedDate;
import org.cryptomator.cryptofs.fh.OpenFileSize;
Expand Down Expand Up @@ -42,6 +43,7 @@ public class CleartextFileChannel extends AbstractFileChannel {
private final FileHeader fileHeader;
private final Cryptor cryptor;
private final ChunkCache chunkCache;
private final BufferPool bufferPool;
private final EffectiveOpenOptions options;
private final AtomicLong fileSize;
private final AtomicReference<Instant> lastModified;
Expand All @@ -52,12 +54,13 @@ public class CleartextFileChannel extends AbstractFileChannel {
private boolean mustWriteHeader;

@Inject
public CleartextFileChannel(FileChannel ciphertextFileChannel, FileHeader fileHeader, @MustWriteHeader boolean mustWriteHeader, ReadWriteLock readWriteLock, Cryptor cryptor, ChunkCache chunkCache, EffectiveOpenOptions options, @OpenFileSize AtomicLong fileSize, @OpenFileModifiedDate AtomicReference<Instant> lastModified, Supplier<BasicFileAttributeView> attrViewProvider, ExceptionsDuringWrite exceptionsDuringWrite, ChannelCloseListener closeListener, CryptoFileSystemStats stats) {
public CleartextFileChannel(FileChannel ciphertextFileChannel, FileHeader fileHeader, @MustWriteHeader boolean mustWriteHeader, ReadWriteLock readWriteLock, Cryptor cryptor, ChunkCache chunkCache, BufferPool bufferPool, EffectiveOpenOptions options, @OpenFileSize AtomicLong fileSize, @OpenFileModifiedDate AtomicReference<Instant> lastModified, Supplier<BasicFileAttributeView> attrViewProvider, ExceptionsDuringWrite exceptionsDuringWrite, ChannelCloseListener closeListener, CryptoFileSystemStats stats) {
super(readWriteLock);
this.ciphertextFileChannel = ciphertextFileChannel;
this.fileHeader = fileHeader;
this.cryptor = cryptor;
this.chunkCache = chunkCache;
this.bufferPool = bufferPool;
this.options = options;
this.fileSize = fileSize;
this.lastModified = lastModified;
Expand Down Expand Up @@ -104,9 +107,9 @@ protected int readLocked(ByteBuffer dst, long position) throws IOException {
long pos = position + read;
long chunkIndex = pos / payloadSize; // floor by int-truncation
int offsetInChunk = (int) (pos % payloadSize); // known to fit in int, because payloadSize is int
int len = min(dst.remaining(), payloadSize - offsetInChunk); // known to fit in int, because second argument is int
final ChunkData chunkData = chunkCache.get(chunkIndex);
chunkData.copyDataStartingAt(offsetInChunk).to(dst);
ByteBuffer data = chunkCache.get(chunkIndex).data().duplicate().position(offsetInChunk);
int len = min(dst.remaining(), data.remaining()); // known to fit in int, because second argument is int
dst.put(data.limit(data.position() + len));
read += len;
}
dst.limit(origLimit);
Expand Down Expand Up @@ -148,17 +151,21 @@ private long writeLockedInternal(ByteSource src, long position) throws IOExcepti
assert len <= cleartextChunkSize;
if (offsetInChunk == 0 && len == cleartextChunkSize) {
// complete chunk, no need to load and decrypt from file
ChunkData chunkData = ChunkData.emptyWithSize(cleartextChunkSize);
chunkData.copyData().from(src);
chunkCache.set(chunkIndex, chunkData);
ByteBuffer cleartextChunkData = bufferPool.getCleartextBuffer();
src.copyTo(cleartextChunkData);
cleartextChunkData.flip();
Chunk chunk = new Chunk(cleartextChunkData, true);
chunkCache.set(chunkIndex, chunk);
} else {
/*
* TODO performance:
* We don't actually need to read the current data into the cache.
* It would suffice if store the written data and do reading when storing the chunk.
*/
ChunkData chunkData = chunkCache.get(chunkIndex);
chunkData.copyDataStartingAt(offsetInChunk).from(src);
Chunk chunk = chunkCache.get(chunkIndex);
chunk.data().limit(Math.max(chunk.data().limit(), offsetInChunk + len)); // increase limit (if needed)
src.copyTo(chunk.data().duplicate().position(offsetInChunk)); // work on duplicate using correct offset
chunk.dirty().set(true);
}
written += len;
}
Expand Down Expand Up @@ -190,7 +197,9 @@ protected void truncateLocked(long newSize) throws IOException {
long indexOfLastChunk = (newSize + cleartextChunkSize - 1) / cleartextChunkSize - 1;
int sizeOfIncompleteChunk = (int) (newSize % cleartextChunkSize); // known to fit in int, because cleartextChunkSize is int
if (sizeOfIncompleteChunk > 0) {
chunkCache.get(indexOfLastChunk).truncate(sizeOfIncompleteChunk);
var chunk = chunkCache.get(indexOfLastChunk);
chunk.data().limit(sizeOfIncompleteChunk);
chunk.dirty().set(true);
}
long ciphertextFileSize = cryptor.fileHeaderCryptor().headerSize() + cryptor.fileContentCryptor().ciphertextSize(newSize);
chunkCache.invalidateAll(); // make sure no chunks _after_ newSize exist that would otherwise be written during the next cache eviction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,11 @@
import javax.inject.Inject;
import javax.inject.Named;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.stream.Stream;

import static org.cryptomator.cryptofs.common.Constants.DIR_FILE_NAME;
Expand Down Expand Up @@ -49,7 +46,7 @@ public C9rConflictResolver(Cryptor cryptor, @Named("dirId") String dirId, VaultC
public Stream<Node> process(Node node) {
Preconditions.checkArgument(node.extractedCiphertext != null, "Can only resolve conflicts if extractedCiphertext is set");
Preconditions.checkArgument(node.cleartextName != null, "Can only resolve conflicts if cleartextName is set");

String canonicalCiphertextFileName = node.extractedCiphertext + Constants.CRYPTOMATOR_FILE_SUFFIX;
if (node.fullCiphertextFileName.equals(canonicalCiphertextFileName)) {
// not a conflict:
Expand Down Expand Up @@ -85,9 +82,9 @@ private Stream<Node> resolveConflict(Node conflicting, Path canonicalPath) throw
/**
* Resolves a conflict by renaming the conflicting file.
*
* @param canonicalPath The path to the original (conflict-free) file.
* @param canonicalPath The path to the original (conflict-free) file.
* @param conflictingPath The path to the potentially conflicting file.
* @param cleartext The cleartext name of the conflicting file.
* @param cleartext The cleartext name of the conflicting file.
* @return The newly created Node after renaming the conflicting file.
* @throws IOException
*/
Expand Down Expand Up @@ -121,7 +118,7 @@ private Node renameConflictingFile(Path canonicalPath, Path conflictingPath, Str
/**
* Tries to resolve a conflicting file without renaming the file. If successful, only the file with the canonical path will exist afterwards.
*
* @param canonicalPath The path to the original (conflict-free) resource (must not exist).
* @param canonicalPath The path to the original (conflict-free) resource (must not exist).
* @param conflictingPath The path to the potentially conflicting file (known to exist).
* @return <code>true</code> if the conflict has been resolved.
* @throws IOException
Expand All @@ -144,8 +141,8 @@ private boolean resolveConflictTrivially(Path canonicalPath, Path conflictingPat
}

/**
* @param conflictingPath Path to a potentially conflicting file supposedly containing a directory id
* @param canonicalPath Path to the canonical file containing a directory id
* @param conflictingPath Path to a potentially conflicting file supposedly containing a directory id
* @param canonicalPath Path to the canonical file containing a directory id
* @param numBytesToCompare Number of bytes to read from each file and compare to each other.
* @return <code>true</code> if the first <code>numBytesToCompare</code> bytes are equal in both files.
* @throws IOException If an I/O exception occurs while reading either file.
Expand All @@ -154,16 +151,8 @@ private boolean hasSameFileContent(Path conflictingPath, Path canonicalPath, int
if (!Files.isDirectory(conflictingPath.getParent()) || !Files.isDirectory(canonicalPath.getParent())) {
return false;
}
// TODO replace by Files.mismatch() when using JDK > 12
try (ReadableByteChannel in1 = Files.newByteChannel(conflictingPath, StandardOpenOption.READ); //
ReadableByteChannel in2 = Files.newByteChannel(canonicalPath, StandardOpenOption.READ)) {
ByteBuffer buf1 = ByteBuffer.allocate(numBytesToCompare);
ByteBuffer buf2 = ByteBuffer.allocate(numBytesToCompare);
int read1 = in1.read(buf1);
int read2 = in2.read(buf2);
buf1.flip();
buf2.flip();
return read1 == read2 && buf1.compareTo(buf2) == 0;
try {
return -1L == Files.mismatch(conflictingPath, canonicalPath);
} catch (NoSuchFileException e) {
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/cryptomator/cryptofs/dir/C9sInflator.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ public Stream<Node> process(Node node) {
node.cleartextName = cryptor.fileNameCryptor().decryptFilename(BaseEncoding.base64Url(), node.extractedCiphertext, dirId);
return Stream.of(node);
} catch (AuthenticationFailedException e) {
LOG.warn(node.ciphertextPath + "'s inflated filename could not be decrypted.");
LOG.warn("{}'s inflated filename could not be decrypted.", node.ciphertextPath);
return Stream.empty();
} catch (IOException e) {
LOG.warn(node.ciphertextPath + " could not be inflated.");
LOG.warn("{} could not be inflated.",node.ciphertextPath);
return Stream.empty();
}
}
Expand Down
57 changes: 57 additions & 0 deletions src/main/java/org/cryptomator/cryptofs/fh/BufferPool.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.cryptomator.cryptofs.fh;

import org.cryptomator.cryptofs.CryptoFileSystemScoped;
import org.cryptomator.cryptolib.api.Cryptor;

import javax.inject.Inject;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
* A pool of ByteBuffers for cleartext and ciphertext chunks to avoid on-heap allocation.
*/
@CryptoFileSystemScoped
public class BufferPool {

private final int ciphertextChunkSize;
private final int cleartextChunkSize;
private final Queue<WeakReference<ByteBuffer>> ciphertextBuffers = new ConcurrentLinkedQueue<>();
private final Queue<WeakReference<ByteBuffer>> cleartextBuffers = new ConcurrentLinkedQueue<>();

@Inject
public BufferPool(Cryptor cryptor) {
this.ciphertextChunkSize = cryptor.fileContentCryptor().ciphertextChunkSize();
this.cleartextChunkSize = cryptor.fileContentCryptor().cleartextChunkSize();
}

private Optional<ByteBuffer> dequeueFrom(Queue<WeakReference<ByteBuffer>> queue) {
WeakReference<ByteBuffer> ref;
while ((ref = queue.poll()) != null) {
ByteBuffer cached = ref.get();
if (cached != null) {
cached.clear();
return Optional.of(cached);
}
}
return Optional.empty();
}

public ByteBuffer getCiphertextBuffer() {
return dequeueFrom(ciphertextBuffers).orElseGet(() -> ByteBuffer.allocate(ciphertextChunkSize));
}

public ByteBuffer getCleartextBuffer() {
return dequeueFrom(cleartextBuffers).orElseGet(() -> ByteBuffer.allocate(cleartextChunkSize));
}

public void recycle(ByteBuffer buffer) {
if (buffer.capacity() == ciphertextChunkSize) {
ciphertextBuffers.add(new WeakReference<>(buffer));
} else if (buffer.capacity() == cleartextChunkSize) {
cleartextBuffers.add(new WeakReference<>(buffer));
}
}
}
Loading

0 comments on commit d9c8bef

Please sign in to comment.