Skip to content

Commit b5b3036

Browse files
committed
Merge branch 'release/2.7.2'
2 parents 4d6a56b + 80d18f1 commit b5b3036

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+735
-340
lines changed

.github/workflows/publish-central.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,4 @@ jobs:
3636
MAVEN_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
3737
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
3838
MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
39-
MAVEN_GPG_KEY_FINGERPRINT: "58117AFA1F85B3EEC154677D615D449FE6E6A235"
39+
MAVEN_GPG_KEY_FINGERPRINT: ${{ vars.RELEASES_GPG_KEY_FINGERPRINT }}

.github/workflows/publish-github.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2424
MAVEN_GPG_PASSPHRASE: ${{ secrets.RELEASES_GPG_PASSPHRASE }}
2525
MAVEN_GPG_KEY: ${{ secrets.RELEASES_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
26-
MAVEN_GPG_KEY_FINGERPRINT: "58117AFA1F85B3EEC154677D615D449FE6E6A235"
26+
MAVEN_GPG_KEY_FINGERPRINT: ${{ vars.RELEASES_GPG_KEY_FINGERPRINT }}
2727
- name: Slack Notification
2828
uses: rtCamp/action-slack-notify@v2
2929
env:

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<modelVersion>4.0.0</modelVersion>
33
<groupId>org.cryptomator</groupId>
44
<artifactId>cryptofs</artifactId>
5-
<version>2.7.1</version>
5+
<version>2.7.2</version>
66
<name>Cryptomator Crypto Filesystem</name>
77
<description>This library provides the Java filesystem provider used by Cryptomator.</description>
88
<url>https://github.com/cryptomator/cryptofs</url>

src/main/java/org/cryptomator/cryptofs/CryptoFileSystem.java

+22
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.cryptomator.cryptofs;
22

3+
import org.cryptomator.cryptofs.common.Constants;
4+
35
import java.io.IOException;
46
import java.nio.file.FileSystem;
57
import java.nio.file.Files;
@@ -41,6 +43,26 @@ public abstract class CryptoFileSystem extends FileSystem {
4143
*/
4244
public abstract Path getCiphertextPath(Path cleartextPath) throws IOException;
4345

46+
/**
47+
* Computes from a valid,encrypted node (file or folder) its cleartext name.
48+
* <p>
49+
* Due to the structure of a vault, an encrypted node is valid if:
50+
* <ul>
51+
* <li>the path points into the vault (duh!)</li>
52+
* <li>the "file" extension is {@value Constants#CRYPTOMATOR_FILE_SUFFIX} or {@value Constants#DEFLATED_FILE_SUFFIX}</li>
53+
* <li>the node name is at least {@value Constants#MIN_CIPHER_NAME_LENGTH} characters long</li>
54+
* <li>it is located at depth 4 from the vault storage root, i.e. d/AB/CDEFG...XYZ/validFile.c9r</li>
55+
* </ul>
56+
*
57+
* @param ciphertextNode path to the ciphertext file or directory
58+
* @return the cleartext name of the ciphertext file or directory
59+
* @throws java.nio.file.NoSuchFileException if the ciphertextFile does not exist
60+
* @throws IOException if an I/O error occurs reading the ciphertext files
61+
* @throws IllegalArgumentException if {@param ciphertextNode} is not a valid ciphertext content node of the vault
62+
* @throws UnsupportedOperationException if the directory containing the {@param ciphertextNode} does not have a {@value Constants#DIR_ID_BACKUP_FILE_NAME} file
63+
*/
64+
public abstract String getCleartextName(Path ciphertextNode) throws IOException, UnsupportedOperationException;
65+
4466
/**
4567
* Provides file system performance statistics.
4668
*

src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java

+15-8
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
import org.cryptomator.cryptofs.common.DeletingFileVisitor;
1818
import org.cryptomator.cryptofs.common.FinallyUtil;
1919
import org.cryptomator.cryptofs.dir.CiphertextDirectoryDeleter;
20-
import org.cryptomator.cryptofs.dir.DirectoryStreamFilters;
2120
import org.cryptomator.cryptofs.dir.DirectoryStreamFactory;
21+
import org.cryptomator.cryptofs.dir.DirectoryStreamFilters;
2222
import org.cryptomator.cryptofs.fh.OpenCryptoFiles;
2323
import org.cryptomator.cryptolib.api.Cryptor;
2424

@@ -95,16 +95,17 @@ class CryptoFileSystemImpl extends CryptoFileSystem {
9595

9696
private final CryptoPath rootPath;
9797
private final CryptoPath emptyPath;
98+
private final FileNameDecryptor fileNameDecryptor;
9899

99100
private volatile boolean open = true;
100101

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

130131
this.rootPath = cryptoPathFactory.rootFor(this);
131132
this.emptyPath = cryptoPathFactory.emptyFor(this);
133+
this.fileNameDecryptor = fileNameDecryptor;
132134
}
133135

134136
@Override
@@ -151,6 +153,11 @@ public Path getCiphertextPath(Path cleartextPath) throws IOException {
151153
}
152154
}
153155

156+
@Override
157+
public String getCleartextName(Path ciphertextNode) throws IOException, UnsupportedOperationException {
158+
return fileNameDecryptor.decryptFilename(ciphertextNode);
159+
}
160+
154161
@Override
155162
public CryptoFileSystemStats getStats() {
156163
return stats;
@@ -331,7 +338,7 @@ void createDirectory(CryptoPath cleartextDir, FileAttribute<?>... attrs) throws
331338
// create dir if and only if the dirFile has been created right now (not if it has been created before):
332339
try {
333340
Files.createDirectories(ciphertextDir.path());
334-
dirIdBackup.execute(ciphertextDir);
341+
dirIdBackup.write(ciphertextDir);
335342
ciphertextPath.persistLongFileName();
336343
} catch (IOException e) {
337344
// make sure there is no orphan dir file:

src/main/java/org/cryptomator/cryptofs/CryptoFileSystemProvider.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope
155155
Path vaultCipherRootPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2));
156156
Files.createDirectories(vaultCipherRootPath);
157157
// create dirId backup:
158-
DirectoryIdBackup.backupManually(cryptor, new CiphertextDirectory(Constants.ROOT_DIR_ID, vaultCipherRootPath));
158+
DirectoryIdBackup.write(cryptor, new CiphertextDirectory(Constants.ROOT_DIR_ID, vaultCipherRootPath));
159159
} finally {
160160
Arrays.fill(rawKey, (byte) 0x00);
161161
}

src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java

+2-22
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,11 @@ class CryptoFileSystems {
3434

3535
private final ConcurrentMap<Path, CryptoFileSystemImpl> fileSystems = new ConcurrentHashMap<>();
3636
private final CryptoFileSystemComponent.Factory cryptoFileSystemComponentFactory;
37-
private final FileSystemCapabilityChecker capabilityChecker;
3837
private final SecureRandom csprng;
3938

4039
@Inject
41-
public CryptoFileSystems(CryptoFileSystemComponent.Factory cryptoFileSystemComponentFactory, FileSystemCapabilityChecker capabilityChecker, SecureRandom csprng) {
40+
public CryptoFileSystems(CryptoFileSystemComponent.Factory cryptoFileSystemComponentFactory, SecureRandom csprng) {
4241
this.cryptoFileSystemComponentFactory = cryptoFileSystemComponentFactory;
43-
this.capabilityChecker = capabilityChecker;
4442
this.csprng = csprng;
4543
}
4644

@@ -53,13 +51,12 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT
5351
try (Masterkey key = properties.keyLoader().loadKey(keyId)) {
5452
var config = configLoader.verify(key.getEncoded(), Constants.VAULT_VERSION);
5553
backupVaultConfigFile(normalizedPathToVault, properties);
56-
var adjustedProperties = adjustForCapabilities(pathToVault, properties);
5754
var cryptor = CryptorProvider.forScheme(config.getCipherCombo()).provide(key.copy(), csprng);
5855
try {
5956
checkVaultRootExistence(pathToVault, cryptor);
6057
return fileSystems.compute(normalizedPathToVault, (path, fs) -> {
6158
if (fs == null) {
62-
return cryptoFileSystemComponentFactory.create(cryptor, config, provider, normalizedPathToVault, adjustedProperties).cryptoFileSystem();
59+
return cryptoFileSystemComponentFactory.create(cryptor, config, provider, normalizedPathToVault, properties).cryptoFileSystem();
6360
} else {
6461
throw new FileSystemAlreadyExistsException();
6562
}
@@ -123,23 +120,6 @@ private void backupVaultConfigFile(Path pathToVault, CryptoFileSystemProperties
123120
BackupHelper.attemptBackup(vaultConfigFile);
124121
}
125122

126-
private CryptoFileSystemProperties adjustForCapabilities(Path pathToVault, CryptoFileSystemProperties originalProperties) throws FileSystemCapabilityChecker.MissingCapabilityException {
127-
if (!originalProperties.readonly()) {
128-
try {
129-
capabilityChecker.assertWriteAccess(pathToVault);
130-
return originalProperties;
131-
} catch (FileSystemCapabilityChecker.MissingCapabilityException e) {
132-
capabilityChecker.assertReadAccess(pathToVault);
133-
LOG.warn("No write access to vault. Fallback to read-only access.");
134-
Set<CryptoFileSystemProperties.FileSystemFlags> flags = EnumSet.copyOf(originalProperties.flags());
135-
flags.add(CryptoFileSystemProperties.FileSystemFlags.READONLY);
136-
return CryptoFileSystemProperties.cryptoFileSystemPropertiesFrom(originalProperties).withFlags(flags).build();
137-
}
138-
} else {
139-
return originalProperties;
140-
}
141-
}
142-
143123
public void remove(CryptoFileSystemImpl cryptoFileSystem) {
144124
fileSystems.values().remove(cryptoFileSystem);
145125
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package org.cryptomator.cryptofs;
22

33
import org.cryptomator.cryptofs.common.Constants;
4+
import org.cryptomator.cryptolib.api.CryptoException;
45
import org.cryptomator.cryptolib.api.Cryptor;
6+
import org.cryptomator.cryptolib.common.DecryptingReadableByteChannel;
57
import org.cryptomator.cryptolib.common.EncryptingWritableByteChannel;
68

79
import javax.inject.Inject;
@@ -10,31 +12,32 @@
1012
import java.nio.channels.ByteChannel;
1113
import java.nio.charset.StandardCharsets;
1214
import java.nio.file.Files;
15+
import java.nio.file.Path;
1316
import java.nio.file.StandardOpenOption;
1417

1518
/**
16-
* Single purpose class to back up the directory id of an encrypted directory when it is created.
19+
* Single purpose class to read or write the directory id backup of an encrypted directory.
1720
*/
1821
@CryptoFileSystemScoped
1922
public class DirectoryIdBackup {
2023

21-
private Cryptor cryptor;
24+
private final Cryptor cryptor;
2225

2326
@Inject
2427
public DirectoryIdBackup(Cryptor cryptor) {
2528
this.cryptor = cryptor;
2629
}
2730

2831
/**
29-
* Performs the backup operation for the given {@link CiphertextDirectory} object.
32+
* Writes the dirId backup file for the {@link CiphertextDirectory} object.
3033
* <p>
31-
* The directory id is written via an encrypting channel to the file {@link CiphertextDirectory#path()} /{@value Constants#DIR_BACKUP_FILE_NAME}.
34+
* The directory id is written via an encrypting channel to the file {@link CiphertextDirectory#path()}.resolve({@value Constants#DIR_ID_BACKUP_FILE_NAME});
3235
*
3336
* @param ciphertextDirectory The cipher dir object containing the dir id and the encrypted content root
3437
* @throws IOException if an IOException is raised during the write operation
3538
*/
36-
public void execute(CiphertextDirectory ciphertextDirectory) throws IOException {
37-
try (var channel = Files.newByteChannel(ciphertextDirectory.path().resolve(Constants.DIR_BACKUP_FILE_NAME), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); //
39+
public void write(CiphertextDirectory ciphertextDirectory) throws IOException {
40+
try (var channel = Files.newByteChannel(getBackupFilePath(ciphertextDirectory.path()), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); //
3841
var encryptingChannel = wrapEncryptionAround(channel, cryptor)) {
3942
encryptingChannel.write(ByteBuffer.wrap(ciphertextDirectory.dirId().getBytes(StandardCharsets.US_ASCII)));
4043
}
@@ -43,16 +46,65 @@ public void execute(CiphertextDirectory ciphertextDirectory) throws IOException
4346
/**
4447
* Static method to explicitly back up the directory id for a specified ciphertext directory.
4548
*
46-
* @param cryptor The cryptor to be used
49+
* @param cryptor The cryptor to be used for encryption
4750
* @param ciphertextDirectory A {@link CiphertextDirectory} for which the dirId should be back up'd.
4851
* @throws IOException when the dirId file already exists, or it cannot be written to.
4952
*/
50-
public static void backupManually(Cryptor cryptor, CiphertextDirectory ciphertextDirectory) throws IOException {
51-
new DirectoryIdBackup(cryptor).execute(ciphertextDirectory);
53+
public static void write(Cryptor cryptor, CiphertextDirectory ciphertextDirectory) throws IOException {
54+
new DirectoryIdBackup(cryptor).write(ciphertextDirectory);
5255
}
5356

5457

55-
static EncryptingWritableByteChannel wrapEncryptionAround(ByteChannel channel, Cryptor cryptor) {
58+
/**
59+
* Reads the dirId backup file and retrieves the directory id from it.
60+
*
61+
* @param ciphertextContentDir path of a ciphertext <strong>content</strong> directory
62+
* @return a byte array containing the directory id
63+
* @throws IOException if the dirId backup file cannot be read
64+
* @throws CryptoException if the content of dirId backup file cannot be decrypted/authenticated
65+
* @throws IllegalStateException if the directory id exceeds {@value Constants#MAX_DIR_ID_LENGTH} chars
66+
*/
67+
public byte[] read(Path ciphertextContentDir) throws IOException, CryptoException, IllegalStateException {
68+
var dirIdBackupFile = getBackupFilePath(ciphertextContentDir);
69+
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
70+
71+
try (var channel = Files.newByteChannel(dirIdBackupFile, StandardOpenOption.READ); //
72+
var decryptingChannel = wrapDecryptionAround(channel, cryptor)) {
73+
int read = decryptingChannel.read(dirIdBuffer);
74+
if (read < 0 || read > Constants.MAX_DIR_ID_LENGTH) {
75+
throw new IllegalStateException("Read directory id exceeds the maximum length of %d characters".formatted(Constants.MAX_DIR_ID_LENGTH));
76+
}
77+
}
78+
79+
var dirId = new byte[dirIdBuffer.position()];
80+
dirIdBuffer.get(0, dirId);
81+
return dirId;
82+
}
83+
84+
/**
85+
* Static method to explicitly retrieve the directory id of a ciphertext directory from the dirId backup file
86+
*
87+
* @param cryptor The cryptor to be used for decryption
88+
* @param ciphertextContentDir path of a ciphertext <strong>content</strong> directory
89+
* @return a byte array containing the directory id
90+
* @throws IOException if the dirId backup file cannot be read
91+
* @throws CryptoException if the content of dirId backup file cannot be decrypted/authenticated
92+
* @throws IllegalStateException if the directory id exceeds {@value Constants#MAX_DIR_ID_LENGTH} chars
93+
*/
94+
public static byte[] read(Cryptor cryptor, Path ciphertextContentDir) throws IOException, CryptoException, IllegalStateException {
95+
return new DirectoryIdBackup(cryptor).read(ciphertextContentDir);
96+
}
97+
98+
99+
private static Path getBackupFilePath(Path ciphertextContentDir) {
100+
return ciphertextContentDir.resolve(Constants.DIR_ID_BACKUP_FILE_NAME);
101+
}
102+
103+
DecryptingReadableByteChannel wrapDecryptionAround(ByteChannel channel, Cryptor cryptor) {
104+
return new DecryptingReadableByteChannel(channel, cryptor, true);
105+
}
106+
107+
EncryptingWritableByteChannel wrapEncryptionAround(ByteChannel channel, Cryptor cryptor) {
56108
return new EncryptingWritableByteChannel(channel, cryptor);
57109
}
58110
}

0 commit comments

Comments
 (0)