Remove files with non-decryptable names from dir listings

This commit is contained in:
Sebastian Stenzel
2016-05-13 18:59:06 +02:00
parent 6fc343ea12
commit 6c18103662
4 changed files with 36 additions and 31 deletions

View File

@@ -2,14 +2,14 @@ package org.cryptomator.filesystem.crypto;
import static org.cryptomator.filesystem.crypto.Constants.DIR_SUFFIX;
import java.util.Optional;
import java.util.UUID;
import java.util.function.UnaryOperator;
import java.util.function.Function;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.crypto.engine.CryptoException;
import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.Folder;
import org.slf4j.Logger;
@@ -21,10 +21,10 @@ final class ConflictResolver {
private static final int UUID_FIRST_GROUP_STRLEN = 8;
private final Pattern encryptedNamePattern;
private final UnaryOperator<String> nameDecryptor;
private final UnaryOperator<String> nameEncryptor;
private final Function<String, Optional<String>> nameDecryptor;
private final Function<String, Optional<String>> nameEncryptor;
public ConflictResolver(Pattern encryptedNamePattern, UnaryOperator<String> nameDecryptor, UnaryOperator<String> nameEncryptor) {
public ConflictResolver(Pattern encryptedNamePattern, Function<String, Optional<String>> nameDecryptor, Function<String, Optional<String>> nameEncryptor) {
this.encryptedNamePattern = encryptedNamePattern;
this.nameDecryptor = nameDecryptor;
this.nameEncryptor = nameEncryptor;
@@ -47,8 +47,8 @@ final class ConflictResolver {
private File resolveConflict(File conflictingFile, MatchResult matchResult) {
String ciphertext = matchResult.group();
boolean isDirectory = conflictingFile.name().substring(matchResult.end()).startsWith(DIR_SUFFIX);
try {
String cleartext = nameDecryptor.apply(ciphertext);
Optional<String> cleartext = nameDecryptor.apply(ciphertext);
if (cleartext.isPresent()) {
Folder folder = conflictingFile.parent().get();
File canonicalFile = folder.file(isDirectory ? ciphertext + DIR_SUFFIX : ciphertext);
if (canonicalFile.exists()) {
@@ -57,8 +57,8 @@ final class ConflictResolver {
String conflictId;
do {
conflictId = createConflictId();
String alternativeCleartext = cleartext + " (Conflict " + conflictId + ")";
String alternativeCiphertext = nameEncryptor.apply(alternativeCleartext);
String alternativeCleartext = cleartext.get() + " (Conflict " + conflictId + ")";
String alternativeCiphertext = nameEncryptor.apply(alternativeCleartext).get();
alternativeFile = folder.file(isDirectory ? alternativeCiphertext + DIR_SUFFIX : alternativeCiphertext);
} while (alternativeFile.exists());
LOG.info("Detected conflict {}:\n{}\n{}", conflictId, canonicalFile, conflictingFile);
@@ -68,7 +68,7 @@ final class ConflictResolver {
conflictingFile.moveTo(canonicalFile);
return canonicalFile;
}
} catch (CryptoException e) {
} else {
// not decryptable; false positive
return conflictingFile;
}

View File

@@ -8,8 +8,6 @@
*******************************************************************************/
package org.cryptomator.filesystem.crypto;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.UncheckedIOException;
import java.nio.file.FileAlreadyExistsException;
import java.util.Optional;
@@ -27,9 +25,7 @@ class CryptoFile extends CryptoNode implements File {
@Override
protected Optional<String> encryptedName() {
return parent().get().getDirectoryId().map(s -> s.getBytes(UTF_8)).map(parentDirId -> {
return cryptor.getFilenameCryptor().encryptFilename(name(), parentDirId);
});
return parent().get().encryptChildName(name());
}
@Override

View File

@@ -25,15 +25,19 @@ import org.apache.commons.lang3.StringUtils;
import org.cryptomator.common.LazyInitializer;
import org.cryptomator.common.WeakValuedCache;
import org.cryptomator.common.streams.AutoClosingStream;
import org.cryptomator.crypto.engine.CryptoException;
import org.cryptomator.crypto.engine.Cryptor;
import org.cryptomator.filesystem.Deleter;
import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.Node;
import org.cryptomator.io.FileContents;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class CryptoFolder extends CryptoNode implements Folder {
private static final Logger LOG = LoggerFactory.getLogger(CryptoFolder.class);
private final WeakValuedCache<String, CryptoFolder> folders = WeakValuedCache.usingLoader(this::newFolder);
private final WeakValuedCache<String, CryptoFile> files = WeakValuedCache.usingLoader(this::newFile);
private final AtomicReference<String> directoryId = new AtomicReference<>();
@@ -49,9 +53,7 @@ class CryptoFolder extends CryptoNode implements Folder {
@Override
protected Optional<String> encryptedName() {
if (parent().isPresent()) {
return parent().get().getDirectoryId().map(s -> s.getBytes(UTF_8)).map(parentDirId -> {
return cryptor.getFilenameCryptor().encryptFilename(name(), parentDirId) + DIR_SUFFIX;
});
return parent().get().encryptChildName(name()).map(s -> s + DIR_SUFFIX);
} else {
return Optional.of(cryptor.getFilenameCryptor().encryptFilename(name()) + DIR_SUFFIX);
}
@@ -95,24 +97,31 @@ class CryptoFolder extends CryptoNode implements Folder {
return (File file) -> encryptedNamePattern.matcher(file.name()).find();
}
private String decryptChildName(String ciphertextFileName) {
final byte[] dirId = getDirectoryId().get().getBytes(UTF_8);
return cryptor.getFilenameCryptor().decryptFilename(ciphertextFileName, dirId);
Optional<String> decryptChildName(String ciphertextFileName) {
return getDirectoryId().map(s -> s.getBytes(UTF_8)).map(dirId -> {
try {
return cryptor.getFilenameCryptor().decryptFilename(ciphertextFileName, dirId);
} catch (CryptoException e) {
LOG.warn("Filename decryption of {} failed: {}", ciphertextFileName, e.getMessage());
return null;
}
});
}
private String encryptChildName(String cleartextFileName) {
final byte[] dirId = getDirectoryId().get().getBytes(UTF_8);
return cryptor.getFilenameCryptor().encryptFilename(cleartextFileName, dirId);
Optional<String> encryptChildName(String cleartextFileName) {
return getDirectoryId().map(s -> s.getBytes(UTF_8)).map(dirId -> {
return cryptor.getFilenameCryptor().encryptFilename(cleartextFileName, dirId);
});
}
@Override
public Stream<CryptoFile> files() {
return nonConflictingFiles().map(File::name).filter(endsWithDirSuffix().negate()).map(this::decryptChildName).map(this::file);
return nonConflictingFiles().map(File::name).filter(endsWithDirSuffix().negate()).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::file);
}
@Override
public Stream<CryptoFolder> folders() {
return nonConflictingFiles().map(File::name).filter(endsWithDirSuffix()).map(this::removeDirSuffix).map(this::decryptChildName).map(this::folder);
return nonConflictingFiles().map(File::name).filter(endsWithDirSuffix()).map(this::removeDirSuffix).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::folder);
}
private Predicate<String> endsWithDirSuffix() {

View File

@@ -2,7 +2,7 @@ package org.cryptomator.filesystem.crypto;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.function.UnaryOperator;
import java.util.function.Function;
import java.util.regex.Pattern;
import org.apache.commons.codec.binary.Base32;
@@ -29,8 +29,8 @@ public class ConflictResolverTest {
public void setup() {
Pattern base32Pattern = Pattern.compile("([A-Z0-9]{8})*[A-Z0-9=]{8}");
BaseNCodec base32 = new Base32();
UnaryOperator<String> decode = (s) -> new String(base32.decode(s), StandardCharsets.UTF_8);
UnaryOperator<String> encode = (s) -> base32.encodeAsString(s.getBytes(StandardCharsets.UTF_8));
Function<String, Optional<String>> decode = (s) -> Optional.of(new String(base32.decode(s), StandardCharsets.UTF_8));
Function<String, Optional<String>> encode = (s) -> Optional.of(base32.encodeAsString(s.getBytes(StandardCharsets.UTF_8)));
conflictResolver = new ConflictResolver(base32Pattern, decode, encode);
folder = Mockito.mock(Folder.class);
@@ -41,8 +41,8 @@ public class ConflictResolverTest {
resolved = Mockito.mock(File.class);
unrelatedFile = Mockito.mock(File.class);
String canonicalFileName = encode.apply("test name");
String canonicalFolderName = encode.apply("test name") + Constants.DIR_SUFFIX;
String canonicalFileName = encode.apply("test name").get();
String canonicalFolderName = canonicalFileName + Constants.DIR_SUFFIX;
String conflictingFileName = canonicalFileName + " (version 2)";
String conflictingFolderName = canonicalFolderName + " (version 2)";
String unrelatedName = "notBa$e32!";