Skip to content

Commit aca202b

Browse files
committed
Switch to byte array for directory ids to avoid lossy data conversion.
1 parent cbbe9b2 commit aca202b

File tree

12 files changed

+65
-47
lines changed

12 files changed

+65
-47
lines changed

core/src/main/java/ch/cyberduck/core/PathAttributes.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ public class PathAttributes extends Attributes implements Serializable {
158158
/**
159159
* Unique identifier for cryptomator
160160
*/
161-
private String directoryId;
161+
private byte[] directoryId;
162162

163163
private Map<String, String> custom = Collections.emptyMap();
164164

@@ -526,11 +526,11 @@ public PathAttributes withLockId(final String lockId) {
526526
return this;
527527
}
528528

529-
public String getDirectoryId() {
529+
public byte[] getDirectoryId() {
530530
return directoryId;
531531
}
532532

533-
public void setDirectoryId(final String directoryId) {
533+
public void setDirectoryId(final byte[] directoryId) {
534534
this.directoryId = directoryId;
535535
}
536536

cryptomator/src/main/java/ch/cyberduck/core/cryptomator/AbstractVault.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import org.cryptomator.cryptolib.api.FileContentCryptor;
3737
import org.cryptomator.cryptolib.api.FileHeaderCryptor;
3838

39-
import java.nio.charset.StandardCharsets;
4039
import java.util.EnumSet;
4140
import java.util.regex.Matcher;
4241
import java.util.regex.Pattern;
@@ -134,7 +133,7 @@ public Path encrypt(Session<?> session, Path file, boolean metadata) throws Back
134133
return this.encrypt(session, file, file.attributes().getDirectoryId(), metadata);
135134
}
136135

137-
public Path encrypt(Session<?> session, Path file, String directoryId, boolean metadata) throws BackgroundException {
136+
public Path encrypt(Session<?> session, Path file, byte[] directoryId, boolean metadata) throws BackgroundException {
138137
final Path encrypted;
139138
if(file.isFile() || metadata) {
140139
if(file.getType().contains(Path.Type.vault)) {
@@ -212,7 +211,7 @@ public Path decrypt(final Session<?> session, final Path file) throws Background
212211
try {
213212
final String cleartextFilename = this.getFileNameCryptor().decryptFilename(
214213
this.getVersion() == VAULT_VERSION_DEPRECATED ? BaseEncoding.base32() : BaseEncoding.base64Url(),
215-
ciphertext, file.getParent().attributes().getDirectoryId().getBytes(StandardCharsets.UTF_8));
214+
ciphertext, file.getParent().attributes().getDirectoryId());
216215
final PathAttributes attributes = new PathAttributes(file.attributes());
217216
if(this.isDirectory(inflated)) {
218217
if(Permission.EMPTY != attributes.getPermission()) {

cryptomator/src/main/java/ch/cyberduck/core/cryptomator/ContentReader.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
import ch.cyberduck.core.Session;
2222
import ch.cyberduck.core.exception.BackgroundException;
2323
import ch.cyberduck.core.features.Read;
24+
import ch.cyberduck.core.io.StreamCopier;
2425
import ch.cyberduck.core.transfer.TransferStatus;
2526

2627
import org.apache.commons.io.IOUtils;
2728

29+
import java.io.ByteArrayOutputStream;
2830
import java.io.IOException;
2931
import java.io.InputStream;
3032
import java.io.InputStreamReader;
@@ -49,6 +51,19 @@ public String read(final Path file) throws BackgroundException {
4951
}
5052
}
5153

54+
public byte[] readBytes(final Path file) throws BackgroundException {
55+
final Read read = session._getFeature(Read.class);
56+
final TransferStatus status = new TransferStatus().withLength(file.attributes().getSize());
57+
try (final InputStream in = read.read(file, status, new DisabledConnectionCallback())) {
58+
final ByteArrayOutputStream out = new ByteArrayOutputStream();
59+
new StreamCopier(status, status).transfer(in, out);
60+
return out.toByteArray();
61+
}
62+
catch(IOException e) {
63+
throw new DefaultIOExceptionMappingService().map(e);
64+
}
65+
}
66+
5267
public Reader getReader(final Path file) throws BackgroundException {
5368
final Read read = session._getFeature(Read.class);
5469
return new InputStreamReader(read.read(file, new TransferStatus().withLength(file.attributes().getSize()), new DisabledConnectionCallback()), StandardCharsets.UTF_8);

cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptoDirectory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public interface CryptoDirectory {
3232
* @param type File type
3333
* @return Encrypted filename
3434
*/
35-
String toEncrypted(Session<?> session, String directoryId, String filename, EnumSet<Path.Type> type) throws BackgroundException;
35+
String toEncrypted(Session<?> session, byte[] directoryId, String filename, EnumSet<Path.Type> type) throws BackgroundException;
3636

3737
/**
3838
* Get encrypted reference for clear text directory path.
@@ -41,7 +41,7 @@ public interface CryptoDirectory {
4141
* @param directoryId Directory ID or null to read directory id from metadata file
4242
* @param directory Clear text
4343
*/
44-
Path toEncrypted(Session<?> session, String directoryId, Path directory) throws BackgroundException;
44+
Path toEncrypted(Session<?> session, byte[] directoryId, Path directory) throws BackgroundException;
4545

4646
/**
4747
* Remove from cache

cryptomator/src/main/java/ch/cyberduck/core/cryptomator/CryptorCache.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.cryptomator.cryptolib.api.AuthenticationFailedException;
2121
import org.cryptomator.cryptolib.api.FileNameCryptor;
2222

23+
import java.nio.ByteBuffer;
2324
import java.util.Arrays;
2425
import java.util.Objects;
2526

@@ -29,7 +30,7 @@ public class CryptorCache {
2930

3031
public static final BaseEncoding BASE32 = BaseEncoding.base32();
3132

32-
private final LRUCache<String, String> directoryIdCache = LRUCache.build(250);
33+
private final LRUCache<ByteBuffer, String> directoryIdCache = LRUCache.build(250);
3334
private final LRUCache<CacheKey, String> decryptCache = LRUCache.build(5000);
3435
private final LRUCache<CacheKey, String> encryptCache = LRUCache.build(5000);
3536

@@ -39,11 +40,12 @@ public CryptorCache(final FileNameCryptor impl) {
3940
this.impl = impl;
4041
}
4142

42-
public String hashDirectoryId(final String cleartextDirectoryId) {
43-
if(!directoryIdCache.contains(cleartextDirectoryId)) {
44-
directoryIdCache.put(cleartextDirectoryId, impl.hashDirectoryId(cleartextDirectoryId));
43+
public String hashDirectoryId(final byte[] cleartextDirectoryId) {
44+
final ByteBuffer wrap = ByteBuffer.wrap(cleartextDirectoryId);
45+
if(!directoryIdCache.contains(wrap)) {
46+
directoryIdCache.put(wrap, impl.hashDirectoryId(cleartextDirectoryId));
4547
}
46-
return directoryIdCache.get(cleartextDirectoryId);
48+
return directoryIdCache.get(wrap);
4749
}
4850

4951
public String encryptFilename(final BaseEncoding encoding, final String cleartextName, final byte[] associatedData) {

cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoBulkFeature.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
import org.cryptomator.cryptolib.api.FileHeader;
3636

37+
import java.nio.charset.StandardCharsets;
3738
import java.util.ArrayList;
3839
import java.util.Comparator;
3940
import java.util.HashMap;
@@ -84,7 +85,7 @@ public int compare(final Map.Entry<TransferItem, TransferStatus> o1, final Map.E
8485
switch(type) {
8586
case upload:
8687
// Preset directory ID for new folders to avert lookup with not found failure in directory ID provider
87-
final String directoryId = random.random();
88+
final byte[] directoryId = random.random().getBytes(StandardCharsets.US_ASCII);
8889
encrypted.put(new TransferItem(cryptomator.encrypt(session, file, directoryId, false), local), status);
8990
break;
9091
default:

cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDirectoryV6Feature.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ public CryptoDirectoryV6Feature(final Session<?> session, final Directory<Reply>
5353

5454
@Override
5555
public Path mkdir(final Path folder, final TransferStatus status) throws BackgroundException {
56-
final Path encrypt = vault.encrypt(session, folder, random.random(), false);
57-
final String directoryId = encrypt.attributes().getDirectoryId();
56+
final Path encrypt = vault.encrypt(session, folder, random.random().getBytes(StandardCharsets.US_ASCII), false);
57+
final byte[] directoryId = encrypt.attributes().getDirectoryId();
5858
// Create metadata file for directory
5959
final Path directoryMetadataFile = vault.encrypt(session, folder, true);
6060
log.debug("Write metadata {} for folder {}", directoryMetadataFile, folder);
61-
new ContentWriter(session).write(directoryMetadataFile, directoryId.getBytes(StandardCharsets.UTF_8));
61+
new ContentWriter(session).write(directoryMetadataFile, directoryId);
6262
final Path intermediate = encrypt.getParent();
6363
if(!session._getFeature(Find.class).find(intermediate)) {
6464
session._getFeature(Directory.class).mkdir(intermediate, new TransferStatus().withRegion(status.getRegion()));

cryptomator/src/main/java/ch/cyberduck/core/cryptomator/features/CryptoDirectoryV7Feature.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,16 @@ public CryptoDirectoryV7Feature(final Session<?> session, final Directory<Reply>
5555

5656
@Override
5757
public Path mkdir(final Path folder, final TransferStatus status) throws BackgroundException {
58-
final Path encrypt = vault.encrypt(session, folder, random.random(), false);
59-
final String directoryId = encrypt.attributes().getDirectoryId();
58+
final Path encrypt = vault.encrypt(session, folder, random.random().getBytes(StandardCharsets.US_ASCII), false);
59+
final byte[] directoryId = encrypt.attributes().getDirectoryId();
6060
// Create metadata file for directory
6161
final Path directoryMetadataFolder = session._getFeature(Directory.class).mkdir(vault.encrypt(session, folder, true),
6262
new TransferStatus().withRegion(status.getRegion()));
6363
final Path directoryMetadataFile = new Path(directoryMetadataFolder,
6464
CryptoDirectoryV7Provider.DIRECTORY_METADATAFILE,
6565
EnumSet.of(Path.Type.file));
6666
log.debug("Write metadata {} for folder {}", directoryMetadataFile, folder);
67-
new ContentWriter(session).write(directoryMetadataFile, directoryId.getBytes(StandardCharsets.UTF_8));
67+
new ContentWriter(session).write(directoryMetadataFile, directoryId);
6868
final Path intermediate = encrypt.getParent();
6969
if(!session._getFeature(Find.class).find(intermediate)) {
7070
session._getFeature(Directory.class).mkdir(intermediate, new TransferStatus().withRegion(status.getRegion()));

cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV6Provider.java

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@
3232
import ch.cyberduck.core.exception.NotfoundException;
3333
import ch.cyberduck.core.preferences.PreferencesFactory;
3434

35-
import org.apache.commons.lang3.StringUtils;
35+
import org.apache.commons.lang3.ArrayUtils;
3636
import org.apache.logging.log4j.LogManager;
3737
import org.apache.logging.log4j.Logger;
3838

3939
import java.nio.charset.StandardCharsets;
40+
import java.util.Arrays;
4041
import java.util.EnumSet;
4142
import java.util.concurrent.locks.Lock;
4243
import java.util.concurrent.locks.ReentrantLock;
@@ -45,7 +46,7 @@ public class CryptoDirectoryV6Provider implements CryptoDirectory {
4546
private static final Logger log = LogManager.getLogger(CryptoDirectoryV6Provider.class);
4647

4748
private static final String DATA_DIR_NAME = "d";
48-
private static final String ROOT_DIR_ID = StringUtils.EMPTY;
49+
private static final byte[] ROOT_DIR_ID = new byte[0];
4950

5051
private final Path dataRoot;
5152
private final Path home;
@@ -57,7 +58,7 @@ public class CryptoDirectoryV6Provider implements CryptoDirectory {
5758

5859
private final Lock lock = new ReentrantLock();
5960

60-
private final LRUCache<CacheReference<Path>, String> cache = LRUCache.build(
61+
private final LRUCache<CacheReference<Path>, byte[]> cache = LRUCache.build(
6162
PreferencesFactory.get().getInteger("cryptomator.cache.size"));
6263

6364
public CryptoDirectoryV6Provider(final Path vault, final CryptoFilename filenameProvider, final CryptorCache filenameCryptor) {
@@ -68,15 +69,15 @@ public CryptoDirectoryV6Provider(final Path vault, final CryptoFilename filename
6869
}
6970

7071
@Override
71-
public String toEncrypted(final Session<?> session, final String directoryId, final String filename, final EnumSet<Path.Type> type) throws BackgroundException {
72+
public String toEncrypted(final Session<?> session, final byte[] directoryId, final String filename, final EnumSet<Path.Type> type) throws BackgroundException {
7273
final String prefix = type.contains(Path.Type.directory) ? CryptoVault.DIR_PREFIX : "";
73-
final String ciphertextName = prefix + filenameCryptor.encryptFilename(CryptorCache.BASE32, filename, directoryId.getBytes(StandardCharsets.UTF_8));
74+
final String ciphertextName = prefix + filenameCryptor.encryptFilename(CryptorCache.BASE32, filename, directoryId);
7475
log.debug("Encrypted filename {} to {}", filename, ciphertextName);
7576
return filenameProvider.deflate(session, ciphertextName);
7677
}
7778

7879
@Override
79-
public Path toEncrypted(final Session<?> session, final String directoryId, final Path directory) throws BackgroundException {
80+
public Path toEncrypted(final Session<?> session, final byte[] directoryId, final Path directory) throws BackgroundException {
8081
if(!directory.isDirectory()) {
8182
throw new NotfoundException(directory.getAbsolute());
8283
}
@@ -86,7 +87,7 @@ public Path toEncrypted(final Session<?> session, final String directoryId, fina
8687
attributes.withVersionId(null);
8788
attributes.withFileId(null);
8889
// Remember random directory id for use in vault
89-
final String id = this.toDirectoryId(session, directory, directoryId);
90+
final byte[] id = this.toDirectoryId(session, directory, directoryId);
9091
log.debug("Use directory ID '{}' for folder {}", id, directory);
9192
attributes.setDirectoryId(id);
9293
attributes.setDecrypted(directory);
@@ -102,18 +103,18 @@ public Path toEncrypted(final Session<?> session, final String directoryId, fina
102103
throw new NotfoundException(directory.getAbsolute());
103104
}
104105

105-
private String toDirectoryId(final Session<?> session, final Path directory, final String directoryId) throws BackgroundException {
106+
private byte[] toDirectoryId(final Session<?> session, final Path directory, final byte[] directoryId) throws BackgroundException {
106107
if(new SimplePathPredicate(home).test(directory)) {
107108
return ROOT_DIR_ID;
108109
}
109-
if(StringUtils.isBlank(directoryId)) {
110+
if(ArrayUtils.isEmpty(directoryId)) {
110111
if(cache.contains(new SimplePathPredicate(directory))) {
111112
return cache.get(new SimplePathPredicate(directory));
112113
}
113114
try {
114115
log.debug("Acquire lock for {}", directory);
115116
lock.lock();
116-
final String id = this.load(session, directory);
117+
final byte[] id = this.load(session, directory);
117118
cache.put(new SimplePathPredicate(directory), id);
118119
return id;
119120
}
@@ -125,27 +126,27 @@ private String toDirectoryId(final Session<?> session, final Path directory, fin
125126
cache.put(new SimplePathPredicate(directory), directoryId);
126127
}
127128
else {
128-
final String existing = cache.get(new SimplePathPredicate(directory));
129-
if(!existing.equals(directoryId)) {
129+
final byte[] existing = cache.get(new SimplePathPredicate(directory));
130+
if(!Arrays.equals(existing, directoryId)) {
130131
log.warn("Do not override already cached id {} with {}", existing, directoryId);
131132
}
132133
}
133134
return cache.get(new SimplePathPredicate(directory));
134135
}
135136

136-
protected String load(final Session<?> session, final Path directory) throws BackgroundException {
137+
protected byte[] load(final Session<?> session, final Path directory) throws BackgroundException {
137138
final Path parent = this.toEncrypted(session, directory.getParent().attributes().getDirectoryId(), directory.getParent());
138139
final String cleartextName = directory.getName();
139140
final String ciphertextName = this.toEncrypted(session, parent.attributes().getDirectoryId(), cleartextName, EnumSet.of(Path.Type.directory));
140141
// Read directory id from file
141142
try {
142143
log.debug("Read directory ID for folder {} from {}", directory, ciphertextName);
143144
final Path metadataFile = new Path(parent, ciphertextName, EnumSet.of(Path.Type.file, Path.Type.encrypted));
144-
return new ContentReader(session).read(metadataFile);
145+
return new ContentReader(session).readBytes(metadataFile);
145146
}
146147
catch(NotfoundException e) {
147148
log.warn("Missing directory ID for folder {}", directory);
148-
return random.random();
149+
return random.random().getBytes(StandardCharsets.US_ASCII);
149150
}
150151
}
151152

cryptomator/src/main/java/ch/cyberduck/core/cryptomator/impl/CryptoDirectoryV7Provider.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,13 @@ public CryptoDirectoryV7Provider(final Path vault, final CryptoFilename filename
5555
}
5656

5757
@Override
58-
public String toEncrypted(final Session<?> session, final String directoryId, final String filename, final EnumSet<Path.Type> type) throws BackgroundException {
59-
final String ciphertextName = filenameCryptor.encryptFilename(BaseEncoding.base64Url(),
60-
filename, directoryId.getBytes(StandardCharsets.UTF_8)) + EXTENSION_REGULAR;
58+
public String toEncrypted(final Session<?> session, final byte[] directoryId, final String filename, final EnumSet<Path.Type> type) throws BackgroundException {
59+
final String ciphertextName = filenameCryptor.encryptFilename(BaseEncoding.base64Url(), filename, directoryId) + EXTENSION_REGULAR;
6160
log.debug("Encrypted filename {} to {}", filename, ciphertextName);
6261
return filenameProvider.deflate(session, ciphertextName);
6362
}
6463

65-
protected String load(final Session<?> session, final Path directory) throws BackgroundException {
64+
protected byte[] load(final Session<?> session, final Path directory) throws BackgroundException {
6665
final Path parent = this.toEncrypted(session, directory.getParent().attributes().getDirectoryId(), directory.getParent());
6766
final String cleartextName = directory.getName();
6867
final String ciphertextName = this.toEncrypted(session, parent.attributes().getDirectoryId(), cleartextName, EnumSet.of(Path.Type.directory));
@@ -71,11 +70,11 @@ protected String load(final Session<?> session, final Path directory) throws Bac
7170
try {
7271
log.debug("Read directory ID for folder {} from {}", directory, ciphertextName);
7372
final Path metadataFile = new Path(metadataParent, CryptoDirectoryV7Provider.DIRECTORY_METADATAFILE, EnumSet.of(Path.Type.file, Path.Type.encrypted));
74-
return new ContentReader(session).read(metadataFile);
73+
return new ContentReader(session).readBytes(metadataFile);
7574
}
7675
catch(NotfoundException e) {
7776
log.warn("Missing directory ID for folder {}", directory);
78-
return random.random();
77+
return random.random().getBytes(StandardCharsets.US_ASCII);
7978
}
8079
}
8180
}

0 commit comments

Comments
 (0)