Skip to content

Commit

Permalink
Merge branch 'release/2.7.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
infeo committed Jan 16, 2025
2 parents 4d6a56b + 80d18f1 commit b5b3036
Show file tree
Hide file tree
Showing 41 changed files with 735 additions and 340 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish-central.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ jobs:
MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
MAVEN_GPG_KEY_FINGERPRINT: "58117AFA1F85B3EEC154677D615D449FE6E6A235"
MAVEN_GPG_KEY_FINGERPRINT: ${{ vars.RELEASES_GPG_KEY_FINGERPRINT }}
2 changes: 1 addition & 1 deletion .github/workflows/publish-github.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
MAVEN_GPG_KEY_FINGERPRINT: "58117AFA1F85B3EEC154677D615D449FE6E6A235"
MAVEN_GPG_KEY_FINGERPRINT: ${{ vars.RELEASES_GPG_KEY_FINGERPRINT }}
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2
env:
Expand Down
2 changes: 1 addition & 1 deletion 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.7.1</version>
<version>2.7.2</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
22 changes: 22 additions & 0 deletions src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.cryptomator.cryptofs;

import org.cryptomator.cryptofs.common.Constants;

import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
Expand Down Expand Up @@ -41,6 +43,26 @@ public abstract class CryptoFileSystem extends FileSystem {
*/
public abstract Path getCiphertextPath(Path cleartextPath) throws IOException;

/**
* Computes from a valid,encrypted node (file or folder) its cleartext name.
* <p>
* Due to the structure of a vault, an encrypted node is valid if:
* <ul>
* <li>the path points into the vault (duh!)</li>
* <li>the "file" extension is {@value Constants#CRYPTOMATOR_FILE_SUFFIX} or {@value Constants#DEFLATED_FILE_SUFFIX}</li>
* <li>the node name is at least {@value Constants#MIN_CIPHER_NAME_LENGTH} characters long</li>
* <li>it is located at depth 4 from the vault storage root, i.e. d/AB/CDEFG...XYZ/validFile.c9r</li>
* </ul>
*
* @param ciphertextNode path to the ciphertext file or directory
* @return the cleartext name of the ciphertext file or directory
* @throws java.nio.file.NoSuchFileException if the ciphertextFile does not exist
* @throws IOException if an I/O error occurs reading the ciphertext files
* @throws IllegalArgumentException if {@param ciphertextNode} is not a valid ciphertext content node of the vault
* @throws UnsupportedOperationException if the directory containing the {@param ciphertextNode} does not have a {@value Constants#DIR_ID_BACKUP_FILE_NAME} file
*/
public abstract String getCleartextName(Path ciphertextNode) throws IOException, UnsupportedOperationException;

/**
* Provides file system performance statistics.
*
Expand Down
23 changes: 15 additions & 8 deletions src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
import org.cryptomator.cryptofs.common.DeletingFileVisitor;
import org.cryptomator.cryptofs.common.FinallyUtil;
import org.cryptomator.cryptofs.dir.CiphertextDirectoryDeleter;
import org.cryptomator.cryptofs.dir.DirectoryStreamFilters;
import org.cryptomator.cryptofs.dir.DirectoryStreamFactory;
import org.cryptomator.cryptofs.dir.DirectoryStreamFilters;
import org.cryptomator.cryptofs.fh.OpenCryptoFiles;
import org.cryptomator.cryptolib.api.Cryptor;

Expand Down Expand Up @@ -95,16 +95,17 @@ class CryptoFileSystemImpl extends CryptoFileSystem {

private final CryptoPath rootPath;
private final CryptoPath emptyPath;
private final FileNameDecryptor fileNameDecryptor;

private volatile boolean open = true;

@Inject
public CryptoFileSystemImpl(CryptoFileSystemProvider provider, CryptoFileSystems cryptoFileSystems, @PathToVault Path pathToVault, Cryptor cryptor,
CryptoFileStore fileStore, CryptoFileSystemStats stats, CryptoPathMapper cryptoPathMapper, CryptoPathFactory cryptoPathFactory,
PathMatcherFactory pathMatcherFactory, DirectoryStreamFactory directoryStreamFactory, DirectoryIdProvider dirIdProvider, DirectoryIdBackup dirIdBackup,
AttributeProvider fileAttributeProvider, AttributeByNameProvider fileAttributeByNameProvider, AttributeViewProvider fileAttributeViewProvider,
OpenCryptoFiles openCryptoFiles, Symlinks symlinks, FinallyUtil finallyUtil, CiphertextDirectoryDeleter ciphertextDirDeleter, ReadonlyFlag readonlyFlag,
CryptoFileSystemProperties fileSystemProperties) {
public CryptoFileSystemImpl(CryptoFileSystemProvider provider, CryptoFileSystems cryptoFileSystems, @PathToVault Path pathToVault, Cryptor cryptor, //
CryptoFileStore fileStore, CryptoFileSystemStats stats, CryptoPathMapper cryptoPathMapper, CryptoPathFactory cryptoPathFactory, //
PathMatcherFactory pathMatcherFactory, DirectoryStreamFactory directoryStreamFactory, DirectoryIdProvider dirIdProvider, DirectoryIdBackup dirIdBackup, //
AttributeProvider fileAttributeProvider, AttributeByNameProvider fileAttributeByNameProvider, AttributeViewProvider fileAttributeViewProvider, //
OpenCryptoFiles openCryptoFiles, Symlinks symlinks, FinallyUtil finallyUtil, CiphertextDirectoryDeleter ciphertextDirDeleter, ReadonlyFlag readonlyFlag, //
CryptoFileSystemProperties fileSystemProperties, FileNameDecryptor fileNameDecryptor) {
this.provider = provider;
this.cryptoFileSystems = cryptoFileSystems;
this.pathToVault = pathToVault;
Expand All @@ -129,6 +130,7 @@ public CryptoFileSystemImpl(CryptoFileSystemProvider provider, CryptoFileSystems

this.rootPath = cryptoPathFactory.rootFor(this);
this.emptyPath = cryptoPathFactory.emptyFor(this);
this.fileNameDecryptor = fileNameDecryptor;
}

@Override
Expand All @@ -151,6 +153,11 @@ public Path getCiphertextPath(Path cleartextPath) throws IOException {
}
}

@Override
public String getCleartextName(Path ciphertextNode) throws IOException, UnsupportedOperationException {
return fileNameDecryptor.decryptFilename(ciphertextNode);
}

@Override
public CryptoFileSystemStats getStats() {
return stats;
Expand Down Expand Up @@ -331,7 +338,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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
24 changes: 2 additions & 22 deletions src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,11 @@ class CryptoFileSystems {

private final ConcurrentMap<Path, CryptoFileSystemImpl> fileSystems = new ConcurrentHashMap<>();
private final CryptoFileSystemComponent.Factory cryptoFileSystemComponentFactory;
private final FileSystemCapabilityChecker capabilityChecker;
private final SecureRandom csprng;

@Inject
public CryptoFileSystems(CryptoFileSystemComponent.Factory cryptoFileSystemComponentFactory, FileSystemCapabilityChecker capabilityChecker, SecureRandom csprng) {
public CryptoFileSystems(CryptoFileSystemComponent.Factory cryptoFileSystemComponentFactory, SecureRandom csprng) {
this.cryptoFileSystemComponentFactory = cryptoFileSystemComponentFactory;
this.capabilityChecker = capabilityChecker;
this.csprng = csprng;
}

Expand All @@ -53,13 +51,12 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT
try (Masterkey key = properties.keyLoader().loadKey(keyId)) {
var config = configLoader.verify(key.getEncoded(), Constants.VAULT_VERSION);
backupVaultConfigFile(normalizedPathToVault, properties);
var adjustedProperties = adjustForCapabilities(pathToVault, properties);
var cryptor = CryptorProvider.forScheme(config.getCipherCombo()).provide(key.copy(), csprng);
try {
checkVaultRootExistence(pathToVault, cryptor);
return fileSystems.compute(normalizedPathToVault, (path, fs) -> {
if (fs == null) {
return cryptoFileSystemComponentFactory.create(cryptor, config, provider, normalizedPathToVault, adjustedProperties).cryptoFileSystem();
return cryptoFileSystemComponentFactory.create(cryptor, config, provider, normalizedPathToVault, properties).cryptoFileSystem();
} else {
throw new FileSystemAlreadyExistsException();
}
Expand Down Expand Up @@ -123,23 +120,6 @@ private void backupVaultConfigFile(Path pathToVault, CryptoFileSystemProperties
BackupHelper.attemptBackup(vaultConfigFile);
}

private CryptoFileSystemProperties adjustForCapabilities(Path pathToVault, CryptoFileSystemProperties originalProperties) throws FileSystemCapabilityChecker.MissingCapabilityException {
if (!originalProperties.readonly()) {
try {
capabilityChecker.assertWriteAccess(pathToVault);
return originalProperties;
} catch (FileSystemCapabilityChecker.MissingCapabilityException e) {
capabilityChecker.assertReadAccess(pathToVault);
LOG.warn("No write access to vault. Fallback to read-only access.");
Set<CryptoFileSystemProperties.FileSystemFlags> flags = EnumSet.copyOf(originalProperties.flags());
flags.add(CryptoFileSystemProperties.FileSystemFlags.READONLY);
return CryptoFileSystemProperties.cryptoFileSystemPropertiesFrom(originalProperties).withFlags(flags).build();
}
} else {
return originalProperties;
}
}

public void remove(CryptoFileSystemImpl cryptoFileSystem) {
fileSystems.values().remove(cryptoFileSystem);
}
Expand Down
72 changes: 62 additions & 10 deletions src/main/java/org/cryptomator/cryptofs/DirectoryIdBackup.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -10,31 +12,32 @@
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) {
this.cryptor = cryptor;
}

/**
* Performs the backup operation for the given {@link CiphertextDirectory} object.
* Writes the dirId backup file for the {@link CiphertextDirectory} object.
* <p>
* 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)));
}
Expand All @@ -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 <strong>content</strong> 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 <strong>content</strong> 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);
}
}
Loading

0 comments on commit b5b3036

Please sign in to comment.