Skip to content

Commit d0943c1

Browse files
authored
Merge pull request #260 from cryptomator/feature/dir-id-file-refactor
Feature: Extend dir id backup class
2 parents 3ca4212 + 4a12322 commit d0943c1

18 files changed

+241
-131
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ void createDirectory(CryptoPath cleartextDir, FileAttribute<?>... attrs) throws
331331
// create dir if and only if the dirFile has been created right now (not if it has been created before):
332332
try {
333333
Files.createDirectories(ciphertextDir.path());
334-
dirIdBackup.execute(ciphertextDir);
334+
dirIdBackup.write(ciphertextDir);
335335
ciphertextPath.persistLongFileName();
336336
} catch (IOException e) {
337337
// make sure there is no orphan dir file:

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

Lines changed: 1 addition & 1 deletion
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
}
Lines changed: 62 additions & 10 deletions
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
}

src/main/java/org/cryptomator/cryptofs/common/Constants.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ private Constants() {
2525
public static final String SYMLINK_FILE_NAME = "symlink.c9r";
2626
public static final String CONTENTS_FILE_NAME = "contents.c9r";
2727
public static final String INFLATED_FILE_NAME = "name.c9s";
28-
public static final String DIR_BACKUP_FILE_NAME = "dirid.c9r";
28+
public static final String DIR_ID_BACKUP_FILE_NAME = "dirid.c9r";
2929

3030
public static final int MAX_SYMLINK_LENGTH = 32767; // max path length on NTFS and FAT32: 32k-1
31-
public static final int MAX_DIR_FILE_LENGTH = 36; // UUIDv4: hex-encoded 16 byte int + 4 hyphens = 36 ASCII chars
31+
public static final int MAX_DIR_ID_LENGTH = 36; // UUIDv4: hex-encoded 16 byte int + 4 hyphens = 36 ASCII chars
3232
public static final int MIN_CIPHER_NAME_LENGTH = 26; //rounded up base64url encoded (16 bytes IV + 0 bytes empty string) + file suffix = 26 ASCII chars
3333

3434
public static final String SEPARATOR = "/";

src/main/java/org/cryptomator/cryptofs/dir/C9rConflictResolver.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import java.util.stream.Stream;
2222

2323
import static org.cryptomator.cryptofs.common.Constants.DIR_FILE_NAME;
24-
import static org.cryptomator.cryptofs.common.Constants.MAX_DIR_FILE_LENGTH;
24+
import static org.cryptomator.cryptofs.common.Constants.MAX_DIR_ID_LENGTH;
2525
import static org.cryptomator.cryptofs.common.Constants.MAX_SYMLINK_LENGTH;
2626
import static org.cryptomator.cryptofs.common.Constants.SYMLINK_FILE_NAME;
2727

@@ -127,7 +127,7 @@ private boolean resolveConflictTrivially(Path canonicalPath, Path conflictingPat
127127
if (!Files.exists(canonicalPath)) {
128128
Files.move(conflictingPath, canonicalPath); // boom. conflict solved.
129129
return true;
130-
} else if (hasSameFileContent(conflictingPath.resolve(DIR_FILE_NAME), canonicalPath.resolve(DIR_FILE_NAME), MAX_DIR_FILE_LENGTH)) {
130+
} else if (hasSameFileContent(conflictingPath.resolve(DIR_FILE_NAME), canonicalPath.resolve(DIR_FILE_NAME), MAX_DIR_ID_LENGTH)) {
131131
LOG.info("Removing conflicting directory {} (identical to {})", conflictingPath, canonicalPath);
132132
MoreFiles.deleteRecursively(conflictingPath, RecursiveDeleteOption.ALLOW_INSECURE);
133133
return true;

src/main/java/org/cryptomator/cryptofs/dir/DirectoryStreamFilters.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77

88
public interface DirectoryStreamFilters {
99

10-
static DirectoryStream.Filter<Path> EXCLUDE_DIR_ID_BACKUP = p -> !p.equals(p.resolveSibling(Constants.DIR_BACKUP_FILE_NAME));
10+
static DirectoryStream.Filter<Path> EXCLUDE_DIR_ID_BACKUP = p -> !p.equals(p.resolveSibling(Constants.DIR_ID_BACKUP_FILE_NAME));
1111

1212
}

src/main/java/org/cryptomator/cryptofs/health/dirid/DirIdCheck.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public void check(Path pathToVault, VaultConfig config, Masterkey masterkey, Cry
6262
if (foundDir) {
6363
iter.remove();
6464
var expectedDirVaultRel = Path.of(Constants.DATA_DIR_NAME).resolve(expectedDir);
65-
if (Files.exists(pathToVault.resolve(expectedDirVaultRel).resolve(Constants.DIR_BACKUP_FILE_NAME))) {
65+
if (Files.exists(pathToVault.resolve(expectedDirVaultRel).resolve(Constants.DIR_ID_BACKUP_FILE_NAME))) {
6666
resultCollector.accept(new HealthyDir(dirId, dirFile, expectedDirVaultRel));
6767
} else {
6868
resultCollector.accept(new MissingDirIdBackup(dirId, expectedDirVaultRel));
@@ -116,7 +116,7 @@ private FileVisitResult visitDirFile(Path dirFile, BasicFileAttributes attrs) th
116116
return FileVisitResult.CONTINUE;
117117
}
118118

119-
if (attrs.size() > Constants.MAX_DIR_FILE_LENGTH) {
119+
if (attrs.size() > Constants.MAX_DIR_ID_LENGTH) {
120120
LOG.warn("Encountered dir.c9r file of size {}", attrs.size());
121121
resultCollector.accept(new ObeseDirFile(dirFile, attrs.size()));
122122
} else if (attrs.size() == 0) {

src/main/java/org/cryptomator/cryptofs/health/dirid/MissingContentDir.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ void fix(Path pathToVault, Cryptor cryptor) throws IOException {
5151
var dirIdHash = cryptor.fileNameCryptor().hashDirectoryId(dirId);
5252
Path dirPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirIdHash.substring(0, 2)).resolve(dirIdHash.substring(2, 32));
5353
Files.createDirectories(dirPath);
54-
DirectoryIdBackup.backupManually(cryptor, new CiphertextDirectory(dirId, dirPath));
54+
DirectoryIdBackup.write(cryptor, new CiphertextDirectory(dirId, dirPath));
5555
}
5656

5757
@Override

src/main/java/org/cryptomator/cryptofs/health/dirid/MissingDirIdBackup.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import java.util.Optional;
1313

1414
/**
15-
* The dir id backup file {@value org.cryptomator.cryptofs.common.Constants#DIR_BACKUP_FILE_NAME} is missing.
15+
* The dir id backup file {@value org.cryptomator.cryptofs.common.Constants#DIR_ID_BACKUP_FILE_NAME} is missing.
1616
*/
1717
public record MissingDirIdBackup(String dirId, Path contentDir) implements DiagnosticResult {
1818

@@ -29,7 +29,7 @@ public String toString() {
2929
//visible for testing
3030
void fix(Path pathToVault, Cryptor cryptor) throws IOException {
3131
Path absCipherDir = pathToVault.resolve(contentDir);
32-
DirectoryIdBackup.backupManually(cryptor, new CiphertextDirectory(dirId, absCipherDir));
32+
DirectoryIdBackup.write(cryptor, new CiphertextDirectory(dirId, absCipherDir));
3333
}
3434

3535
@Override

src/main/java/org/cryptomator/cryptofs/health/dirid/ObeseDirFile.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public Severity getSeverity() {
2828

2929
@Override
3030
public String toString() {
31-
return String.format("Unexpected file size of %s: %d should be ≤ %d", dirFile, size, Constants.MAX_DIR_FILE_LENGTH);
31+
return String.format("Unexpected file size of %s: %d should be ≤ %d", dirFile, size, Constants.MAX_DIR_ID_LENGTH);
3232
}
3333

3434
@Override

0 commit comments

Comments
 (0)