Skip to content

Commit

Permalink
Merge branch 'release/2.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
infeo committed Oct 19, 2021
2 parents 8d82f81 + 7f247f7 commit f7046e1
Show file tree
Hide file tree
Showing 38 changed files with 2,539 additions and 13 deletions.
10 changes: 8 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptofs</artifactId>
<version>2.0.2</version>
<name>Cryptomator Crypto Filesystem</name>
<version>2.1.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 Down Expand Up @@ -141,6 +141,12 @@
<groupId>org.apache.maven.plugins</groupId>
<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>--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>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.common.CiphertextFileType;
import org.cryptomator.cryptofs.health.api.HealthCheck;
import org.cryptomator.cryptofs.health.dirid.DirIdCheck;
import org.cryptomator.cryptofs.health.shortened.ShortenedNamesCheck;
import org.cryptomator.cryptofs.health.type.CiphertextFileTypeCheck;

import java.nio.file.spi.FileSystemProvider;

Expand All @@ -17,8 +22,12 @@

exports org.cryptomator.cryptofs;
exports org.cryptomator.cryptofs.common;
exports org.cryptomator.cryptofs.health.api;
exports org.cryptomator.cryptofs.migration;
exports org.cryptomator.cryptofs.migration.api;

uses HealthCheck;

provides HealthCheck with DirIdCheck, CiphertextFileTypeCheck, ShortenedNamesCheck;
provides FileSystemProvider with CryptoFileSystemProvider;
}
21 changes: 10 additions & 11 deletions src/main/java/org/cryptomator/cryptofs/DirectoryIdLoader.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package org.cryptomator.cryptofs;

import com.google.common.cache.CacheLoader;

import javax.inject.Inject;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.UUID;

import javax.inject.Inject;

import com.google.common.cache.CacheLoader;

@CryptoFileSystemScoped
class DirectoryIdLoader extends CacheLoader<Path, String> {

Expand All @@ -24,19 +24,18 @@ public DirectoryIdLoader() {

@Override
public String load(Path dirFilePath) throws IOException {
try (FileChannel ch = FileChannel.open(dirFilePath, StandardOpenOption.READ)) {
try (FileChannel ch = FileChannel.open(dirFilePath, StandardOpenOption.READ);
InputStream in = Channels.newInputStream(ch)) {
long size = ch.size();
if (size == 0) {
throw new IOException("Invalid, empty directory file: " + dirFilePath);
} else if (size > MAX_DIR_ID_LENGTH) {
throw new IOException("Unexpectedly large directory file: " + dirFilePath);
} else {
assert size <= MAX_DIR_ID_LENGTH; // thus int
ByteBuffer buffer = ByteBuffer.allocate((int) size);
int read = ch.read(buffer);
assert read == size;
buffer.flip();
return StandardCharsets.UTF_8.decode(buffer).toString();
byte[] bytes = in.readNBytes((int) size);
assert bytes.length == size;
return new String(bytes, StandardCharsets.UTF_8);
}
} catch (NoSuchFileException e) {
return UUID.randomUUID().toString();
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/org/cryptomator/cryptofs/VaultConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.cryptomator.cryptofs.common.Constants;
import org.cryptomator.cryptolib.api.CryptorProvider;
Expand Down Expand Up @@ -140,6 +141,22 @@ public URI getKeyId() {
return URI.create(unverifiedConfig.getKeyId());
}

/**
* Gets a value from the tokens header
* @param key Which key to read
* @param clazz Type of the value
* @param <T> Type of the value
* @return The value or <code>null</code> if the key doesn't exist
*/
public <T> T getHeader(String key, Class<T> clazz) {
var claim = unverifiedConfig.getHeaderClaim(key);
try {
return unverifiedConfig.getHeaderClaim(key).as(clazz);
} catch (JWTDecodeException e) {
throw new IllegalArgumentException("Can't convert " + claim + " to type " + clazz.getName(), e);
}
}

/**
* @return The unverified vault version (signature not verified)
*/
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/cryptomator/cryptofs/common/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ private Constants() {
public static final String MASTERKEY_BACKUP_SUFFIX = ".bkup";
public static final String DATA_DIR_NAME = "d";
public static final String ROOT_DIR_ID = "";
public static final String RECOVERY_DIR_ID = "recovery";

public static final String CRYPTOMATOR_FILE_SUFFIX = ".c9r";
public static final String DEFLATED_FILE_SUFFIX = ".c9s";
public static final String DIR_FILE_NAME = "dir.c9r";
Expand All @@ -29,4 +31,5 @@ private Constants() {
public static final int MAX_DIR_FILE_LENGTH = 36; // UUIDv4: hex-encoded 16 byte int + 4 hyphens = 36 ASCII chars

public static final String SEPARATOR = "/";
public static final String RECOVERY_DIR_NAME = "CRYPTOMATOR_RECOVERY";
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ 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);
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/org/cryptomator/cryptofs/health/api/CheckFailed.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.cryptomator.cryptofs.health.api;

public class CheckFailed implements DiagnosticResult {

private final String message;

public CheckFailed(String message) {
this.message = message;
}

@Override
public Severity getSeverity() {
return Severity.CRITICAL;
}

@Override
public String toString() {
return String.format("Check failed: %s", message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.cryptomator.cryptofs.health.api;

public class CommonDetailKeys {

public static final String ENCRYPTED_PATH = "Encrypted Path";
public static final String DIR_ID = "Directory ID";
public static final String DIR_ID_FILE = "Directory ID File";

private CommonDetailKeys() {};

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.cryptomator.cryptofs.health.api;

import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.Masterkey;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Map;

public interface DiagnosticResult {

enum Severity {
/**
* No complains
*/
GOOD,

/**
* Unexpected, but nothing to worry about. May be worth logging
*/
INFO,

/**
* Compromises vault structure, can and should be fixed.
*/
WARN,

/**
* Irreversible damage, no automated way of fixing this issue.
*/
CRITICAL;
}

Severity getSeverity();

/**
* @return A short, human-readable summary of the result.
*/
@Override
String toString();

default void fix(Path pathToVault, VaultConfig config, Masterkey masterkey, Cryptor cryptor) throws IOException {
throw new UnsupportedOperationException("Fix for result" + this.getClass() + " not implemented");
}

/**
* Get more specific info about the result like names of affected resources.
*
* @return A map of strings containing result specific information
*/
default Map<String, String> details() {
return Map.of();
}
}
74 changes: 74 additions & 0 deletions src/main/java/org/cryptomator/cryptofs/health/api/HealthCheck.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.cryptomator.cryptofs.health.api;

import org.cryptomator.cryptofs.VaultConfig;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.Masterkey;
import org.slf4j.LoggerFactory;

import java.nio.file.Path;
import java.util.Collection;
import java.util.ServiceLoader;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public interface HealthCheck {

/**
* @return All known health checks
*/
static Collection<HealthCheck> allChecks() {
return ServiceLoader.load(HealthCheck.class).stream().map(ServiceLoader.Provider::get).toList();
}

/**
* @return A human readable name for this check
*/
default String name() {
var canonicalName = getClass().getCanonicalName();
return canonicalName.substring(canonicalName.lastIndexOf('.')+1);
}

/**
* Checks the vault at the given path.
*
* @param pathToVault Path to the vault's root directory
* @param config The parsed and verified vault config
* @param masterkey The masterkey
* @param cryptor A cryptor initialized for this vault
* @param resultCollector Callback called for each result.
*/
void check(Path pathToVault, VaultConfig config, Masterkey masterkey, Cryptor cryptor, Consumer<DiagnosticResult> resultCollector);

/**
* Invokes the health check on a background thread scheduled using the given executor service. The results will be
* streamed. If the stream gets {@link Stream#close() closed} before it terminates, an attempt is made to cancel
* the health check.
* <p>
* The check blocks if the stream is not consumed
*
* @param pathToVault Path to the vault's root directory
* @param config The parsed and verified vault config
* @param masterkey The masterkey
* @param cryptor A cryptor initialized for this vault
* @param executor An executor service to run the health check
* @return A lazily filled stream of diagnostic results.
*/
default Stream<DiagnosticResult> check(Path pathToVault, VaultConfig config, Masterkey masterkey, Cryptor cryptor, ExecutorService executor) {
var resultSpliterator = new TransferSpliterator<DiagnosticResult>(new PoisonResult());

var task = executor.submit(() -> {
try {
check(pathToVault, config, masterkey, cryptor, resultSpliterator);
} catch (TransferSpliterator.TransferClosedException e) {
LoggerFactory.getLogger(HealthCheck.class).debug("{} cancelled.", name());
} finally {
resultSpliterator.close();
}
});

return StreamSupport.stream(resultSpliterator, false).onClose(() -> task.cancel(true));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.cryptomator.cryptofs.health.api;

record PoisonResult() implements DiagnosticResult {
@Override
public Severity getSeverity() {
return null;
}
}
Loading

0 comments on commit f7046e1

Please sign in to comment.