From 0d969432c27e7852cd2dd20847926f094fd8cf52 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Fri, 15 May 2015 18:13:34 +0200 Subject: [PATCH] some more flat hierarchy fixes --- main/core/pom.xml | 6 + .../jackrabbit/AbstractEncryptedNode.java | 4 +- .../webdav/jackrabbit/CryptoLocator.java | 220 ------------------ .../jackrabbit/CryptoLocatorFactory.java | 102 -------- .../jackrabbit/CryptoResourceFactory.java | 108 +++++---- .../webdav/jackrabbit/EncryptedDir.java | 93 ++++++-- .../EncryptedDirDuringCreation.java | 114 +++++++++ .../webdav/jackrabbit/EncryptedFile.java | 2 +- .../jackrabbit}/FileNamingConventions.java | 53 +++-- .../webdav/jackrabbit/FilenameTranslator.java | 117 ++++++++++ .../jackrabbit}/LongFilenameMetadata.java | 2 +- .../webdav/jackrabbit/NonExistingNode.java | 5 - .../webdav/jackrabbit/WebDavServlet.java | 15 ++ .../crypto/aes256/Aes256Cryptor.java | 77 +----- .../aes256/AesCryptographicConfiguration.java | 8 + .../crypto/aes256/Aes256CryptorTest.java | 32 +-- .../crypto/AbstractCryptorDecorator.java | 15 +- .../java/org/cryptomator/crypto/Cryptor.java | 15 +- .../crypto/CryptorMetadataSupport.java | 31 --- .../crypto/PathCachingCryptorDecorator.java | 9 +- 20 files changed, 453 insertions(+), 575 deletions(-) delete mode 100644 main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoLocator.java delete mode 100644 main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoLocatorFactory.java create mode 100644 main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDirDuringCreation.java rename main/{crypto-aes/src/main/java/org/cryptomator/crypto/aes256 => core/src/main/java/org/cryptomator/webdav/jackrabbit}/FileNamingConventions.java (59%) create mode 100644 main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java rename main/{crypto-aes/src/main/java/org/cryptomator/crypto/aes256 => core/src/main/java/org/cryptomator/webdav/jackrabbit}/LongFilenameMetadata.java (97%) delete mode 100644 main/crypto-api/src/main/java/org/cryptomator/crypto/CryptorMetadataSupport.java diff --git a/main/core/pom.xml b/main/core/pom.xml index 85fd9f6a1..2a28061fd 100644 --- a/main/core/pom.xml +++ b/main/core/pom.xml @@ -64,5 +64,11 @@ org.apache.commons commons-lang3 + + + + com.fasterxml.jackson.core + jackson-databind + diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java index d22e7a3bb..ead8ea246 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java @@ -111,8 +111,6 @@ abstract class AbstractEncryptedNode implements DavResource { } } - protected abstract void determineProperties(); - @Override public DavPropertyName[] getPropertyNames() { return getProperties().getPropertyNames(); @@ -182,7 +180,7 @@ abstract class AbstractEncryptedNode implements DavResource { return null; } - final String parentResource = FilenameUtils.getPath(locator.getResourcePath()); + final String parentResource = FilenameUtils.getPathNoEndSeparator(locator.getResourcePath()); final DavResourceLocator parentLocator = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), parentResource); try { return getFactory().createResource(parentLocator, session); diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoLocator.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoLocator.java deleted file mode 100644 index 3f998870a..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoLocator.java +++ /dev/null @@ -1,220 +0,0 @@ -package org.cryptomator.webdav.jackrabbit; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; - -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; -import org.apache.jackrabbit.webdav.DavResourceLocator; -import org.apache.jackrabbit.webdav.util.EncodeUtil; -import org.apache.logging.log4j.util.Strings; -import org.cryptomator.crypto.Cryptor; -import org.cryptomator.webdav.exceptions.IORuntimeException; - -class CryptoLocator implements DavResourceLocator { - - private final CryptoLocatorFactory factory; - private final Cryptor cryptor; - private final Path rootPath; - private final String prefix; - private final String resourcePath; - - public CryptoLocator(CryptoLocatorFactory factory, Cryptor cryptor, Path rootPath, String prefix, String resourcePath) { - this.factory = factory; - this.cryptor = cryptor; - this.rootPath = rootPath; - this.prefix = prefix; - this.resourcePath = FilenameUtils.normalizeNoEndSeparator(resourcePath, true); - } - - /* path variants */ - - /** - * Returns the decrypted path without any trailing slash. - * - * @see #getHref(boolean) - * @return Plaintext resource path. - */ - @Override - public String getResourcePath() { - return resourcePath; - } - - /** - * Returns the decrypted path and adds URL-encoding. - * - * @param isCollection If true, a trailing slash will be appended. - * @see #getResourcePath() - * @return URL-encoded plaintext resource path. - */ - @Override - public String getHref(boolean isCollection) { - final String encodedResourcePath = EncodeUtil.escapePath(getResourcePath()); - final String href = getPrefix().concat(encodedResourcePath); - assert !href.endsWith("/"); - if (isCollection) { - return href.concat("/"); - } else { - return href; - } - } - - /** - * Returns the encrypted, absolute path on the local filesystem. - * - * @return Absolute, encrypted path as string (use {@link #getEncryptedFilePath()} for {@link Path}s). - */ - @Override - public String getRepositoryPath() { - if (isRootLocation()) { - return getEncryptedRootDirectoryPath(); - } - try { - final String plaintextPath = getResourcePath(); - final String plaintextDir = FilenameUtils.getPathNoEndSeparator(plaintextPath); - final String plaintextFilename = FilenameUtils.getName(plaintextPath); - final String ciphertextDir = cryptor.encryptDirectoryPath(plaintextDir, FileSystems.getDefault().getSeparator()); - final String ciphertextFilename = cryptor.encryptFilename(plaintextFilename, factory); - final String ciphertextPath = ciphertextDir + FileSystems.getDefault().getSeparator() + ciphertextFilename; - return rootPath.resolve(ciphertextPath).toString(); - } catch (IOException e) { - throw new IORuntimeException(e); - } - } - - /** - * Returns the encrypted, absolute path on the local filesystem to the directory represented by this locator. - * - * @return Absolute, encrypted path as string (use {@link #getEncryptedDirectoryPath()} for {@link Path}s). - * @throws IOException - */ - public String getDirectoryPath(boolean create) throws IOException { - if (isRootLocation()) { - return getEncryptedRootDirectoryPath(); - } else { - final List cleartextPathComponents = Arrays.asList(StringUtils.split(getResourcePath(), "/")); - return getEncryptedDirectoryPath(rootPath, cleartextPathComponents, false).toString(); - } - } - - private Path getEncryptedDirectoryPath(Path encryptedParentDirectoryPath, List cleartextSubPathComponents, boolean create) throws IOException { - if (cleartextSubPathComponents.size() == 0) { - return encryptedParentDirectoryPath; - } else { - final String nextPathComponent = cleartextSubPathComponents.get(0); - final List remainingSubPathComponents = cleartextSubPathComponents.subList(1, cleartextSubPathComponents.size()); - final String fullEncryptedSubdirectoryPath = getEncryptedDirectoryPath(encryptedParentDirectoryPath, nextPathComponent, create); - return getEncryptedDirectoryPath(rootPath.resolve(fullEncryptedSubdirectoryPath), remainingSubPathComponents, create); - } - } - - private String getEncryptedDirectoryPath(Path encryptedParentDirectoryPath, String cleartextDirectoryName, boolean create) throws IOException { - final String encryptedDirectoryName = this.cryptor.encryptFilename(cleartextDirectoryName, this.factory); - // TODO file extensions... - final Path directoryFile = encryptedParentDirectoryPath.resolve(encryptedDirectoryName + ".dir"); - if (Files.exists(directoryFile)) { - try (final FileChannel c = FileChannel.open(directoryFile, StandardOpenOption.READ, StandardOpenOption.DSYNC); final FileLock lock = c.lock(0L, Long.MAX_VALUE, true)) { - final ByteBuffer buffer = ByteBuffer.allocate((int) c.size()); - c.read(buffer); - final String directoryUuid = buffer.asCharBuffer().toString(); - return this.cryptor.encryptDirectoryPath(directoryUuid, FileSystems.getDefault().getSeparator()); - } - } else if (create) { - try (final FileChannel c = FileChannel.open(directoryFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC); final FileLock lock = c.lock()) { - final String directoryUuid = UUID.randomUUID().toString(); - final ByteBuffer buf = ByteBuffer.wrap(directoryUuid.getBytes(StandardCharsets.UTF_8)); - c.write(buf); - return this.cryptor.encryptDirectoryPath(directoryUuid, FileSystems.getDefault().getSeparator()); - } - } else { - throw new FileNotFoundException(directoryFile.toString()); - } - } - - private String getEncryptedRootDirectoryPath() { - return this.cryptor.encryptDirectoryPath("", FileSystems.getDefault().getSeparator()); - } - - public Path getEncryptedFilePath() { - return FileSystems.getDefault().getPath(getRepositoryPath()); - } - - public Path getEncryptedDirectoryPath(boolean create) throws IOException { - return FileSystems.getDefault().getPath(getDirectoryPath(create)); - } - - /* other stuff */ - - @Override - public String getPrefix() { - return prefix; - } - - @Override - public String getWorkspacePath() { - return isRootLocation() ? null : ""; - } - - @Override - public String getWorkspaceName() { - return getPrefix(); - } - - @Override - public boolean isSameWorkspace(DavResourceLocator locator) { - return (locator == null) ? false : isSameWorkspace(locator.getWorkspaceName()); - } - - @Override - public boolean isSameWorkspace(String workspaceName) { - return getWorkspaceName().equals(workspaceName); - } - - @Override - public boolean isRootLocation() { - return Strings.isEmpty(getResourcePath()); - } - - @Override - public CryptoLocatorFactory getFactory() { - return factory; - } - - /* hashcode and equals */ - - @Override - public int hashCode() { - final HashCodeBuilder builder = new HashCodeBuilder(); - builder.append(prefix); - builder.append(resourcePath); - return builder.toHashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof CryptoLocator) { - final CryptoLocator other = (CryptoLocator) obj; - final EqualsBuilder builder = new EqualsBuilder(); - builder.append(this.factory, other.factory); - builder.append(this.prefix, other.prefix); - builder.append(this.resourcePath, other.resourcePath); - return builder.isEquals(); - } else { - return false; - } - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoLocatorFactory.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoLocatorFactory.java deleted file mode 100644 index 7e910de80..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoLocatorFactory.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.cryptomator.webdav.jackrabbit; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; - -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.jackrabbit.webdav.DavLocatorFactory; -import org.apache.jackrabbit.webdav.DavResourceLocator; -import org.apache.jackrabbit.webdav.util.EncodeUtil; -import org.cryptomator.crypto.Cryptor; -import org.cryptomator.crypto.CryptorMetadataSupport; -import org.cryptomator.crypto.exceptions.DecryptFailedException; -import org.cryptomator.webdav.exceptions.DecryptFailedRuntimeException; -import org.cryptomator.webdav.exceptions.IORuntimeException; - -class CryptoLocatorFactory implements DavLocatorFactory, CryptorMetadataSupport { - - private final Path dataRoot; - private final Path metadataRoot; - private final Cryptor cryptor; - - CryptoLocatorFactory(String fsRoot, Cryptor cryptor) { - this.dataRoot = FileSystems.getDefault().getPath(fsRoot).resolve("d"); - this.metadataRoot = FileSystems.getDefault().getPath(fsRoot).resolve("m"); - this.cryptor = cryptor; - } - - @Override - public CryptoLocator createResourceLocator(String prefix, String href) { - final String fullPrefix = prefix.endsWith("/") ? prefix : prefix + "/"; - final String relativeHref = StringUtils.removeStart(href, fullPrefix); - - final String resourcePath = EncodeUtil.unescape(StringUtils.removeStart(relativeHref, "/")); - return new CryptoLocator(this, cryptor, dataRoot, fullPrefix, resourcePath); - } - - /** - * @throws DecryptFailedRuntimeException, which should be a checked exception, but Jackrabbit doesn't allow that. - */ - @Override - public CryptoLocator createResourceLocator(String prefix, String workspacePath, String path, boolean isResourcePath) { - if (!isResourcePath) { - throw new UnsupportedOperationException("Can not decrypt " + path + " without knowing plaintext parent path."); - } - final String fullPrefix = prefix.endsWith("/") ? prefix : prefix + "/"; - return new CryptoLocator(this, cryptor, dataRoot, fullPrefix, path); - } - - @Override - public CryptoLocator createResourceLocator(String prefix, String workspacePath, String resourcePath) { - try { - return createResourceLocator(prefix, workspacePath, resourcePath, true); - } catch (DecryptFailedRuntimeException e) { - throw new IllegalStateException("Tried to decrypt resourcePath. Only repositoryPaths can be encrypted.", e); - } - } - - public DavResourceLocator createSubresourceLocator(CryptoLocator parentResource, String ciphertextChildName) { - try { - final String plaintextFilename = cryptor.decryptFilename(ciphertextChildName, this); - final String plaintextPath = FilenameUtils.concat(parentResource.getResourcePath(), plaintextFilename); - return createResourceLocator(parentResource.getPrefix(), parentResource.getWorkspacePath(), plaintextPath); - } catch (IOException e) { - throw new IORuntimeException(e); - } catch (DecryptFailedException e) { - throw new DecryptFailedRuntimeException(e); - } - } - - /* metadata storage */ - - @Override - public void writeMetadata(String metadataGroup, byte[] encryptedMetadata) throws IOException { - final Path metadataDir = metadataRoot.resolve(metadataGroup.substring(0, 2)); - Files.createDirectories(metadataDir); - final Path metadataFile = metadataDir.resolve(metadataGroup.substring(2)); - try (final FileChannel c = FileChannel.open(metadataFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC); final FileLock lock = c.lock()) { - c.write(ByteBuffer.wrap(encryptedMetadata)); - } - } - - @Override - public byte[] readMetadata(String metadataGroup) throws IOException { - final Path metadataDir = metadataRoot.resolve(metadataGroup.substring(0, 2)); - final Path metadataFile = metadataDir.resolve(metadataGroup.substring(2)); - if (!Files.isReadable(metadataFile)) { - return null; - } - try (final FileChannel c = FileChannel.open(metadataFile, StandardOpenOption.READ, StandardOpenOption.DSYNC); final FileLock lock = c.lock(0L, Long.MAX_VALUE, true)) { - final ByteBuffer buffer = ByteBuffer.allocate((int) c.size()); - c.read(buffer); - return buffer.array(); - } - } -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoResourceFactory.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoResourceFactory.java index 44e85364f..bcffb89d6 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoResourceFactory.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoResourceFactory.java @@ -26,42 +26,49 @@ import org.apache.jackrabbit.webdav.lock.LockManager; import org.apache.jackrabbit.webdav.lock.SimpleLockManager; import org.apache.logging.log4j.util.Strings; import org.cryptomator.crypto.Cryptor; -import org.cryptomator.crypto.CryptorMetadataSupport; import org.eclipse.jetty.http.HttpHeader; -public class CryptoResourceFactory implements DavResourceFactory, CryptorMetadataSupport { +public class CryptoResourceFactory implements DavResourceFactory, FileNamingConventions { private final LockManager lockManager = new SimpleLockManager(); private final Cryptor cryptor; private final CryptoWarningHandler cryptoWarningHandler; private final ExecutorService backgroundTaskExecutor; private final Path dataRoot; - private final Path metadataRoot; + private final FilenameTranslator filenameTranslator; - CryptoResourceFactory(Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler, ExecutorService backgroundTaskExecutor, String fsRoot) { + CryptoResourceFactory(Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler, ExecutorService backgroundTaskExecutor, String vaultRoot) { + Path vaultRootPath = FileSystems.getDefault().getPath(vaultRoot); this.cryptor = cryptor; this.cryptoWarningHandler = cryptoWarningHandler; this.backgroundTaskExecutor = backgroundTaskExecutor; - this.dataRoot = FileSystems.getDefault().getPath(fsRoot).resolve("d"); - this.metadataRoot = FileSystems.getDefault().getPath(fsRoot).resolve("m"); + this.dataRoot = vaultRootPath.resolve("d"); + this.filenameTranslator = new FilenameTranslator(cryptor, vaultRootPath); } @Override public final DavResource createResource(DavResourceLocator locator, DavServletRequest request, DavServletResponse response) throws DavException { - if (DavMethods.METHOD_MKCOL.equals(request.getMethod()) || locator.isRootLocation()) { - final Path dirpath = getEncryptedDirectoryPath(locator.getResourcePath()); + if (DavMethods.METHOD_MKCOL.equals(request.getMethod())) { + final String parentResourcePath = FilenameUtils.getFullPathNoEndSeparator(locator.getResourcePath()); + final Path parentDirectoryPath = createEncryptedDirectoryPath(parentResourcePath); + return new EncryptedDirDuringCreation(this, locator, request.getDavSession(), lockManager, cryptor, filenameTranslator, parentDirectoryPath); + } + + if (locator.isRootLocation()) { + final Path dirpath = createEncryptedDirectoryPath(""); return createDirectory(locator, request.getDavSession(), dirpath); } final Path filepath = getEncryptedFilePath(locator.getResourcePath()); + final Path dirFilePath = getEncryptedDirectoryFilePath(locator.getResourcePath()); final String rangeHeader = request.getHeader(HttpHeader.RANGE.asString()); - if (filepath.getFileName().toString().endsWith(".dir")) { - final Path dirpath = getEncryptedDirectoryPath(locator.getResourcePath()); - return createDirectory(locator, request.getDavSession(), dirpath); - } else if (Files.isRegularFile(filepath) && DavMethods.METHOD_GET.equals(request.getMethod()) && rangeHeader != null) { + if (Files.exists(dirFilePath)) { + final Path dirPath = createEncryptedDirectoryPath(locator.getResourcePath()); + return createDirectory(locator, request.getDavSession(), dirPath); + } else if (Files.exists(filepath) && DavMethods.METHOD_GET.equals(request.getMethod()) && rangeHeader != null) { response.setStatus(HttpStatus.SC_PARTIAL_CONTENT); return createFilePart(locator, request.getDavSession(), request, filepath); - } else if (Files.isRegularFile(filepath) || DavMethods.METHOD_PUT.equals(request.getMethod())) { + } else if (Files.exists(filepath) || DavMethods.METHOD_PUT.equals(request.getMethod())) { return createFile(locator, request.getDavSession(), filepath); } else { return createNonExisting(locator, request.getDavSession()); @@ -71,31 +78,63 @@ public class CryptoResourceFactory implements DavResourceFactory, CryptorMetadat @Override public final DavResource createResource(DavResourceLocator locator, DavSession session) throws DavException { if (locator.isRootLocation()) { - final Path dirpath = getEncryptedDirectoryPath(locator.getResourcePath()); + final Path dirpath = createEncryptedDirectoryPath(""); return createDirectory(locator, session, dirpath); } final Path filepath = getEncryptedFilePath(locator.getResourcePath()); - if (filepath.getFileName().toString().endsWith(".dir")) { - final Path dirpath = getEncryptedDirectoryPath(locator.getResourcePath()); - return createDirectory(locator, session, dirpath); - } else if (Files.isRegularFile(filepath)) { + final Path dirFilePath = getEncryptedDirectoryFilePath(locator.getResourcePath()); + if (Files.exists(dirFilePath)) { + final Path dirPath = createEncryptedDirectoryPath(locator.getResourcePath()); + return createDirectory(locator, session, dirPath); + } else if (Files.exists(filepath)) { return createFile(locator, session, filepath); } else { return createNonExisting(locator, session); } } + DavResource createChildDirectoryResource(DavResourceLocator locator, DavSession session, Path existingDirectoryFile) throws DavException { + try { + final String directoryId = new String(readAllBytesAtomically(existingDirectoryFile), StandardCharsets.UTF_8); + final String directory = cryptor.encryptDirectoryPath(directoryId, FileSystems.getDefault().getSeparator()); + final Path dirpath = dataRoot.resolve(directory); + return createDirectory(locator, session, dirpath); + } catch (IOException e) { + throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR, e); + } + } + + DavResource createChildFileResource(DavResourceLocator locator, DavSession session, Path existingFile) throws DavException { + return createFile(locator, session, existingFile); + } + + /** + * @return Absolute file path for a given cleartext file resourcePath. + * @throws IOException + */ + private Path getEncryptedFilePath(String relativeCleartextPath) throws DavException { + final String parentCleartextPath = FilenameUtils.getPathNoEndSeparator(relativeCleartextPath); + final Path parent = createEncryptedDirectoryPath(parentCleartextPath); + final String cleartextFilename = FilenameUtils.getName(relativeCleartextPath); + try { + final String encryptedFilename = filenameTranslator.getEncryptedFilename(cleartextFilename); + return parent.resolve(encryptedFilename); + } catch (IOException e) { + throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR, e); + } + } + /** * @return Absolute file path for a given cleartext file resourcePath. * @throws IOException */ - Path getEncryptedFilePath(String relativeCleartextPath) throws DavException { + private Path getEncryptedDirectoryFilePath(String relativeCleartextPath) throws DavException { final String parentCleartextPath = FilenameUtils.getPathNoEndSeparator(relativeCleartextPath); - final Path parent = getEncryptedDirectoryPath(parentCleartextPath); + final Path parent = createEncryptedDirectoryPath(parentCleartextPath); final String cleartextFilename = FilenameUtils.getName(relativeCleartextPath); try { - final String encryptedFilename = cryptor.encryptFilename(cleartextFilename, this); + final String encryptedFilename = filenameTranslator.getEncryptedDirName(cleartextFilename); return parent.resolve(encryptedFilename); } catch (IOException e) { throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR, e); @@ -106,7 +145,7 @@ public class CryptoResourceFactory implements DavResourceFactory, CryptorMetadat * @return Absolute directory path for a given cleartext directory resourcePath. * @throws IOException */ - Path getEncryptedDirectoryPath(String relativeCleartextPath) throws DavException { + private Path createEncryptedDirectoryPath(String relativeCleartextPath) throws DavException { assert Strings.isEmpty(relativeCleartextPath) || !relativeCleartextPath.endsWith("/"); try { final Path result; @@ -116,9 +155,9 @@ public class CryptoResourceFactory implements DavResourceFactory, CryptorMetadat result = dataRoot.resolve(fixedRootDirectory); } else { final String parentCleartextPath = FilenameUtils.getPathNoEndSeparator(relativeCleartextPath); - final Path parent = getEncryptedDirectoryPath(parentCleartextPath); + final Path parent = createEncryptedDirectoryPath(parentCleartextPath); final String cleartextFilename = FilenameUtils.getName(relativeCleartextPath); - final String encryptedFilename = cryptor.encryptFilename(cleartextFilename, CryptoResourceFactory.this); + final String encryptedFilename = filenameTranslator.getEncryptedDirName(cleartextFilename); final Path directoryFile = parent.resolve(encryptedFilename); final String directoryId; if (Files.exists(directoryFile)) { @@ -146,7 +185,7 @@ public class CryptoResourceFactory implements DavResourceFactory, CryptorMetadat } private EncryptedDir createDirectory(DavResourceLocator locator, DavSession session, Path dirPath) { - return new EncryptedDir(this, locator, session, lockManager, cryptor, dirPath); + return new EncryptedDir(this, locator, session, lockManager, cryptor, filenameTranslator, dirPath); } private NonExistingNode createNonExisting(DavResourceLocator locator, DavSession session) { @@ -169,23 +208,4 @@ public class CryptoResourceFactory implements DavResourceFactory, CryptorMetadat } } - @Override - public void writeMetadata(String metadataGroup, byte[] encryptedMetadata) throws IOException { - final Path metadataDir = metadataRoot.resolve(metadataGroup.substring(0, 2)); - Files.createDirectories(metadataDir); - final Path metadataFile = metadataDir.resolve(metadataGroup.substring(2)); - writeAllBytesAtomically(metadataFile, encryptedMetadata); - } - - @Override - public byte[] readMetadata(String metadataGroup) throws IOException { - final Path metadataDir = metadataRoot.resolve(metadataGroup.substring(0, 2)); - final Path metadataFile = metadataDir.resolve(metadataGroup.substring(2)); - if (!Files.isReadable(metadataFile)) { - return null; - } else { - return readAllBytesAtomically(metadataFile); - } - } - } diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java index 6117bbb96..acf254bb4 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java @@ -10,7 +10,11 @@ package org.cryptomator.webdav.jackrabbit; import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; import java.nio.channels.SeekableByteChannel; +import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; @@ -19,6 +23,7 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.UUID; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; @@ -41,20 +46,23 @@ import org.cryptomator.crypto.exceptions.DecryptFailedException; import org.cryptomator.crypto.exceptions.EncryptFailedException; import org.cryptomator.webdav.exceptions.DavRuntimeException; import org.cryptomator.webdav.exceptions.IORuntimeException; +import org.eclipse.jetty.util.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class EncryptedDir extends AbstractEncryptedNode { +class EncryptedDir extends AbstractEncryptedNode implements FileNamingConventions { private static final Logger LOG = LoggerFactory.getLogger(EncryptedDir.class); private final Path directoryPath; + private final FilenameTranslator filenameTranslator; - public EncryptedDir(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, Path directoryPath) { + public EncryptedDir(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, FilenameTranslator filenameTranslator, Path directoryPath) { super(factory, locator, session, lockManager, cryptor); if (directoryPath == null || !Files.isDirectory(directoryPath)) { throw new IllegalArgumentException("directoryPath must be an existing directory, but was " + directoryPath); } this.directoryPath = directoryPath; + this.filenameTranslator = filenameTranslator; determineProperties(); } @@ -100,47 +108,77 @@ class EncryptedDir extends AbstractEncryptedNode { } } + @Deprecated private void addMemberDir(DavResourceLocator childLocator, InputContext inputContext) throws DavException { + LOG.warn("Invokation of addMemberDir(DavResourceLocator childLocator, InputContext inputContext)"); try { - // the following invokation will create nonexisting directories: - factory.getEncryptedDirectoryPath(childLocator.getResourcePath()); + final String cleartextDirName = FilenameUtils.getName(childLocator.getResourcePath()); + final String ciphertextDirName = filenameTranslator.getEncryptedDirName(cleartextDirName); + final Path dirFilePath = directoryPath.resolve(ciphertextDirName); + final String directoryId; + if (Files.exists(dirFilePath)) { + try (final FileChannel c = FileChannel.open(dirFilePath, StandardOpenOption.READ, StandardOpenOption.DSYNC); final FileLock lock = c.lock(0L, Long.MAX_VALUE, true)) { + final ByteBuffer buffer = ByteBuffer.allocate((int) c.size()); + c.read(buffer); + directoryId = new String(buffer.array(), StandardCharsets.UTF_8); + } + } else { + directoryId = UUID.randomUUID().toString(); + try (final FileChannel c = FileChannel.open(dirFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW, StandardOpenOption.DSYNC); final FileLock lock = c.lock()) { + c.write(ByteBuffer.wrap(directoryId.getBytes(StandardCharsets.UTF_8))); + } + } + final Path directoryPath = filenameTranslator.getEncryptedDirectoryPath(directoryId); + Files.createDirectories(directoryPath); } catch (SecurityException e) { throw new DavException(DavServletResponse.SC_FORBIDDEN, e); + } catch (IOException e) { + throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR, e); } } private void addMemberFile(DavResourceLocator childLocator, InputContext inputContext) throws DavException { - final Path filePath = factory.getEncryptedFilePath(childLocator.getResourcePath()); - try (final SeekableByteChannel channel = Files.newByteChannel(filePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { - cryptor.encryptFile(inputContext.getInputStream(), channel); - } catch (SecurityException e) { - throw new DavException(DavServletResponse.SC_FORBIDDEN, e); + try { + final String cleartextFilename = FilenameUtils.getName(childLocator.getResourcePath()); + final String ciphertextFilename = filenameTranslator.getEncryptedFilename(cleartextFilename); + final Path filePath = directoryPath.resolve(ciphertextFilename); + try (final SeekableByteChannel channel = Files.newByteChannel(filePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { + cryptor.encryptFile(inputContext.getInputStream(), channel); + } catch (SecurityException e) { + throw new DavException(DavServletResponse.SC_FORBIDDEN, e); + } catch (CounterOverflowException e) { + // lets indicate this to the client as a "file too big" error + throw new DavException(DavServletResponse.SC_INSUFFICIENT_SPACE_ON_RESOURCE, e); + } catch (EncryptFailedException e) { + LOG.error("Encryption failed for unknown reasons.", e); + throw new IllegalStateException("Encryption failed for unknown reasons.", e); + } finally { + IOUtils.closeQuietly(inputContext.getInputStream()); + } } catch (IOException e) { LOG.error("Failed to create file.", e); throw new IORuntimeException(e); - } catch (CounterOverflowException e) { - // lets indicate this to the client as a "file too big" error - throw new DavException(DavServletResponse.SC_INSUFFICIENT_SPACE_ON_RESOURCE, e); - } catch (EncryptFailedException e) { - LOG.error("Encryption failed for unknown reasons.", e); - throw new IllegalStateException("Encryption failed for unknown reasons.", e); - } finally { - IOUtils.closeQuietly(inputContext.getInputStream()); } } @Override public DavResourceIterator getMembers() { try { - final DirectoryStream directoryStream = Files.newDirectoryStream(directoryPath, cryptor.getPayloadFilesFilter()); + final DirectoryStream directoryStream = Files.newDirectoryStream(directoryPath, DIRECTORY_CONTENT_FILTER); final List result = new ArrayList<>(); for (final Path childPath : directoryStream) { try { - final String cleartextFilename = cryptor.decryptFilename(childPath.getFileName().toString(), factory); + final String cleartextFilename = filenameTranslator.getCleartextFilename(childPath.getFileName().toString()); final String cleartextFilepath = FilenameUtils.concat(getResourcePath(), cleartextFilename); final DavResourceLocator childLocator = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), cleartextFilepath); - final DavResource resource = factory.createResource(childLocator, session); + final DavResource resource; + if (StringUtil.endsWithIgnoreCase(childPath.getFileName().toString(), DIR_EXT)) { + resource = factory.createChildDirectoryResource(childLocator, session, childPath); + } else { + assert StringUtil.endsWithIgnoreCase(childPath.getFileName().toString(), FILE_EXT); + resource = factory.createChildFileResource(childLocator, session, childPath); + } result.add(resource); } catch (DecryptFailedException e) { LOG.warn("Decryption of resource failed: " + childPath); @@ -168,16 +206,21 @@ class EncryptedDir extends AbstractEncryptedNode { private void removeMember(AbstractEncryptedNode member) throws DavException { try { - if (member.isCollection()) { + final String cleartextFilename = FilenameUtils.getName(member.getResourcePath()); + final String ciphertextFilename; + if (member instanceof EncryptedDir) { + final EncryptedDir subDir = (EncryptedDir) member; // remove sub-members recursively before deleting own directory for (Iterator iterator = member.getMembers(); iterator.hasNext();) { DavResource m = iterator.next(); member.removeMember(m); } - final Path memberDirectoryPath = factory.getEncryptedDirectoryPath(member.getResourcePath()); - Files.deleteIfExists(memberDirectoryPath); + Files.deleteIfExists(subDir.directoryPath); + ciphertextFilename = filenameTranslator.getEncryptedDirName(cleartextFilename); + } else { + ciphertextFilename = filenameTranslator.getEncryptedFilename(cleartextFilename); } - final Path memberPath = factory.getEncryptedFilePath(member.getResourcePath()); + final Path memberPath = directoryPath.resolve(ciphertextFilename); Files.deleteIfExists(memberPath); } catch (FileNotFoundException e) { // no-op @@ -239,7 +282,7 @@ class EncryptedDir extends AbstractEncryptedNode { // do nothing } - @Override + @Deprecated protected void determineProperties() { properties.add(new ResourceType(ResourceType.COLLECTION)); properties.add(new DefaultDavProperty(DavPropertyName.ISCOLLECTION, 1)); diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDirDuringCreation.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDirDuringCreation.java new file mode 100644 index 000000000..cce4bab0b --- /dev/null +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDirDuringCreation.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2014 Sebastian Stenzel + * This file is licensed under the terms of the MIT license. + * See the LICENSE.txt file for more info. + * + * Contributors: + * Sebastian Stenzel - initial API and implementation + ******************************************************************************/ +package org.cryptomator.webdav.jackrabbit; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.time.Instant; +import java.util.UUID; + +import org.apache.commons.io.FilenameUtils; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.DavResource; +import org.apache.jackrabbit.webdav.DavResourceIterator; +import org.apache.jackrabbit.webdav.DavResourceLocator; +import org.apache.jackrabbit.webdav.DavServletResponse; +import org.apache.jackrabbit.webdav.DavSession; +import org.apache.jackrabbit.webdav.io.InputContext; +import org.apache.jackrabbit.webdav.io.OutputContext; +import org.apache.jackrabbit.webdav.lock.LockManager; +import org.cryptomator.crypto.Cryptor; + +class EncryptedDirDuringCreation extends AbstractEncryptedNode { + + private final Path parentDir; + private final FilenameTranslator filenameTranslator; + + public EncryptedDirDuringCreation(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, FilenameTranslator filenameTranslator, Path parentDir) { + super(factory, locator, session, lockManager, cryptor); + this.parentDir = parentDir; + this.filenameTranslator = filenameTranslator; + } + + public void doCreate() throws DavException { + try { + final String cleartextDirName = FilenameUtils.getName(locator.getResourcePath()); + final String ciphertextDirName = filenameTranslator.getEncryptedDirName(cleartextDirName); + final Path dirFilePath = parentDir.resolve(ciphertextDirName); + final String directoryId = UUID.randomUUID().toString(); + try (final FileChannel c = FileChannel.open(dirFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW, StandardOpenOption.DSYNC); final FileLock lock = c.lock()) { + c.write(ByteBuffer.wrap(directoryId.getBytes(StandardCharsets.UTF_8))); + } catch (FileAlreadyExistsException e) { + throw new DavException(DavServletResponse.SC_METHOD_NOT_ALLOWED); + } + final Path directoryPath = filenameTranslator.getEncryptedDirectoryPath(directoryId); + Files.createDirectories(directoryPath); + } catch (IOException e) { + throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR); + } + } + + @Override + protected Path getPhysicalPath() { + throw new UnsupportedOperationException("Resource doesn't exist."); + } + + @Override + public boolean exists() { + return false; + } + + @Override + public boolean isCollection() { + return true; + } + + @Override + public long getModificationTime() { + return Instant.now().toEpochMilli(); + } + + @Override + public void spool(OutputContext outputContext) throws IOException { + throw new UnsupportedOperationException("Resource doesn't exist."); + } + + @Override + public void addMember(DavResource resource, InputContext inputContext) throws DavException { + throw new UnsupportedOperationException("Resource doesn't exist."); + } + + @Override + public DavResourceIterator getMembers() { + throw new UnsupportedOperationException("Resource doesn't exist."); + } + + @Override + public void removeMember(DavResource member) throws DavException { + throw new UnsupportedOperationException("Resource doesn't exist."); + } + + @Override + public void move(AbstractEncryptedNode destination) throws DavException { + throw new UnsupportedOperationException("Resource doesn't exist."); + } + + @Override + public void copy(AbstractEncryptedNode destination, boolean shallow) throws DavException { + throw new UnsupportedOperationException("Resource doesn't exist."); + } + +} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java index d218db846..968478030 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java @@ -100,7 +100,7 @@ class EncryptedFile extends AbstractEncryptedNode { } } - @Override + @Deprecated protected void determineProperties() { if (Files.isRegularFile(filePath)) { try (final SeekableByteChannel channel = Files.newByteChannel(filePath, StandardOpenOption.READ)) { diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/FileNamingConventions.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileNamingConventions.java similarity index 59% rename from main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/FileNamingConventions.java rename to main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileNamingConventions.java index ca6da8eea..7963a84b9 100644 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/FileNamingConventions.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileNamingConventions.java @@ -6,23 +6,18 @@ * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ -package org.cryptomator.crypto.aes256; +package org.cryptomator.webdav.jackrabbit; +import java.io.IOException; +import java.nio.file.DirectoryStream.Filter; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.util.regex.Pattern; -import org.apache.commons.codec.binary.Base32; -import org.apache.commons.codec.binary.BaseNCodec; import org.apache.commons.lang3.StringUtils; interface FileNamingConventions { - /** - * How to encode the encrypted file names safely. Base32 uses only alphanumeric characters and is case-insensitive. - */ - BaseNCodec ENCRYPTED_FILENAME_CODEC = new Base32(); - /** * Maximum path length on some file systems or cloud storage providers is restricted.
* Parent folder path uses up to 58 chars (sha256 -> 32 bytes base32 encoded to 56 bytes + two slashes). That in mind we don't want the total path to be longer than 255 chars.
@@ -31,14 +26,24 @@ interface FileNamingConventions { int ENCRYPTED_FILENAME_LENGTH_LIMIT = 136; /** - * For plaintext file names <= {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT} chars. + * For encrypted directory names <= {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT} chars. */ - String BASIC_FILE_EXT = ".aes"; + String DIR_EXT = ".dir"; /** - * For plaintext file names > {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT} chars. + * For encrypted direcotry names > {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT} chars. */ - String LONG_NAME_FILE_EXT = ".lng.aes"; + String LONG_DIR_EXT = ".lng.dir"; + + /** + * For encrypted file names <= {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT} chars. + */ + String FILE_EXT = ".file"; + + /** + * For encrypted file names > {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT} chars. + */ + String LONG_FILE_EXT = ".lng.file"; /** * Length of prefix in file names > {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT} chars used to determine the corresponding metadata file. @@ -56,11 +61,17 @@ interface FileNamingConventions { @Override public boolean matches(Path path) { final String filename = path.getFileName().toString(); - if (StringUtils.endsWithIgnoreCase(filename, LONG_NAME_FILE_EXT)) { - final String basename = StringUtils.removeEndIgnoreCase(filename, LONG_NAME_FILE_EXT); + if (StringUtils.endsWithIgnoreCase(filename, LONG_FILE_EXT)) { + final String basename = StringUtils.removeEndIgnoreCase(filename, LONG_FILE_EXT); return LONG_NAME_PATTERN.matcher(basename).matches(); - } else if (StringUtils.endsWithIgnoreCase(filename, BASIC_FILE_EXT)) { - final String basename = StringUtils.removeEndIgnoreCase(filename, BASIC_FILE_EXT); + } else if (StringUtils.endsWithIgnoreCase(filename, FILE_EXT)) { + final String basename = StringUtils.removeEndIgnoreCase(filename, FILE_EXT); + return BASIC_NAME_PATTERN.matcher(basename).matches(); + } else if (StringUtils.endsWithIgnoreCase(filename, LONG_DIR_EXT)) { + final String basename = StringUtils.removeEndIgnoreCase(filename, LONG_DIR_EXT); + return LONG_NAME_PATTERN.matcher(basename).matches(); + } else if (StringUtils.endsWithIgnoreCase(filename, DIR_EXT)) { + final String basename = StringUtils.removeEndIgnoreCase(filename, DIR_EXT); return BASIC_NAME_PATTERN.matcher(basename).matches(); } else { return false; @@ -69,4 +80,14 @@ interface FileNamingConventions { }; + /** + * Filter to determine files of interest in encrypted directory. Based on {@link #ENCRYPTED_FILE_MATCHER}. + */ + Filter DIRECTORY_CONTENT_FILTER = new Filter() { + @Override + public boolean accept(Path entry) throws IOException { + return ENCRYPTED_FILE_MATCHER.matches(entry); + } + }; + } diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java new file mode 100644 index 000000000..f951ef3e5 --- /dev/null +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java @@ -0,0 +1,117 @@ +package org.cryptomator.webdav.jackrabbit; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.cryptomator.crypto.Cryptor; +import org.cryptomator.crypto.exceptions.DecryptFailedException; + +import com.fasterxml.jackson.databind.ObjectMapper; + +class FilenameTranslator implements FileNamingConventions { + + private final Cryptor cryptor; + private final Path dataRoot; + private final Path metadataRoot; + private final ObjectMapper objectMapper = new ObjectMapper(); + + public FilenameTranslator(Cryptor cryptor, Path vaultRoot) { + this.cryptor = cryptor; + this.dataRoot = vaultRoot.resolve("d"); + this.metadataRoot = vaultRoot.resolve("m"); + } + + /* file and directory name en/decryption */ + + public Path getEncryptedDirectoryPath(String directoryId) { + final String encrypted = cryptor.encryptDirectoryPath(directoryId, FileSystems.getDefault().getSeparator()); + return dataRoot.resolve(encrypted); + } + + public String getEncryptedFilename(String cleartextFilename) throws IOException { + return getEncryptedFilename(cleartextFilename, FILE_EXT, LONG_FILE_EXT); + } + + public String getEncryptedDirName(String cleartextDirName) throws IOException { + return getEncryptedFilename(cleartextDirName, DIR_EXT, LONG_DIR_EXT); + } + + /** + * Encryption will blow up the filename length due to aes block sizes, IVs and base32 encoding. The result may be too long for some old file systems.
+ * This means that we need a workaround for filenames longer than the limit defined in {@link FileNamingConventions#ENCRYPTED_FILENAME_LENGTH_LIMIT}.
+ *
+ * For filenames longer than this limit we use a metadata file containing the full encrypted paths. For the actual filename a unique alternative is created by concatenating the metadata filename + * and a unique id. + */ + private String getEncryptedFilename(String cleartextFilename, String basicExt, String longExt) throws IOException { + final String ivAndCiphertext = cryptor.encryptFilename(cleartextFilename); + if (ivAndCiphertext.length() + basicExt.length() > ENCRYPTED_FILENAME_LENGTH_LIMIT) { + final String metadataGroup = ivAndCiphertext.substring(0, LONG_NAME_PREFIX_LENGTH); + final LongFilenameMetadata metadata = readMetadata(metadataGroup); + final String longFilename = metadataGroup + metadata.getOrCreateUuidForEncryptedFilename(ivAndCiphertext).toString() + longExt; + this.writeMetadata(metadataGroup, metadata); + return longFilename; + } else { + return ivAndCiphertext + basicExt; + } + } + + public String getCleartextFilename(String encryptedFilename) throws DecryptFailedException, IOException { + final String ciphertext; + if (StringUtils.endsWithIgnoreCase(encryptedFilename, LONG_FILE_EXT)) { + final String basename = StringUtils.removeEndIgnoreCase(encryptedFilename, LONG_FILE_EXT); + final String metadataGroup = basename.substring(0, LONG_NAME_PREFIX_LENGTH); + final String uuid = basename.substring(LONG_NAME_PREFIX_LENGTH); + final LongFilenameMetadata metadata = readMetadata(metadataGroup); + ciphertext = metadata.getEncryptedFilenameForUUID(UUID.fromString(uuid)); + } else if (StringUtils.endsWithIgnoreCase(encryptedFilename, FILE_EXT)) { + ciphertext = StringUtils.removeEndIgnoreCase(encryptedFilename, FILE_EXT); + } else if (StringUtils.endsWithIgnoreCase(encryptedFilename, LONG_DIR_EXT)) { + final String basename = StringUtils.removeEndIgnoreCase(encryptedFilename, LONG_DIR_EXT); + final String metadataGroup = basename.substring(0, LONG_NAME_PREFIX_LENGTH); + final String uuid = basename.substring(LONG_NAME_PREFIX_LENGTH); + final LongFilenameMetadata metadata = readMetadata(metadataGroup); + ciphertext = metadata.getEncryptedFilenameForUUID(UUID.fromString(uuid)); + } else if (StringUtils.endsWithIgnoreCase(encryptedFilename, DIR_EXT)) { + ciphertext = StringUtils.removeEndIgnoreCase(encryptedFilename, DIR_EXT); + } else { + throw new IllegalArgumentException("Unsupported path component: " + encryptedFilename); + } + return cryptor.decryptFilename(ciphertext); + } + + /* Long name metadata files */ + + private void writeMetadata(String metadataGroup, LongFilenameMetadata metadata) throws IOException { + final Path metadataDir = metadataRoot.resolve(metadataGroup.substring(0, 2)); + Files.createDirectories(metadataDir); + final Path metadataFile = metadataDir.resolve(metadataGroup.substring(2)); + try (final FileChannel c = FileChannel.open(metadataFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC); final FileLock lock = c.lock()) { + byte[] bytes = objectMapper.writeValueAsBytes(metadata); + c.write(ByteBuffer.wrap(bytes)); + } + } + + private LongFilenameMetadata readMetadata(String metadataGroup) throws IOException { + final Path metadataDir = metadataRoot.resolve(metadataGroup.substring(0, 2)); + final Path metadataFile = metadataDir.resolve(metadataGroup.substring(2)); + if (!Files.isReadable(metadataFile)) { + return new LongFilenameMetadata(); + } else { + try (final FileChannel c = FileChannel.open(metadataFile, StandardOpenOption.READ, StandardOpenOption.DSYNC); final FileLock lock = c.lock(0L, Long.MAX_VALUE, true)) { + final ByteBuffer buffer = ByteBuffer.allocate((int) c.size()); + c.read(buffer); + return objectMapper.readValue(buffer.array(), LongFilenameMetadata.class); + } + } + } + +} diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/LongFilenameMetadata.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/LongFilenameMetadata.java similarity index 97% rename from main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/LongFilenameMetadata.java rename to main/core/src/main/java/org/cryptomator/webdav/jackrabbit/LongFilenameMetadata.java index db1e5cbbd..77cd116cc 100644 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/LongFilenameMetadata.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/LongFilenameMetadata.java @@ -6,7 +6,7 @@ * Contributors: * Sebastian Stenzel - initial API and implementation ******************************************************************************/ -package org.cryptomator.crypto.aes256; +package org.cryptomator.webdav.jackrabbit; import java.io.Serializable; import java.util.UUID; diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java index 5a2e8e726..27cd3adbc 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java @@ -67,11 +67,6 @@ class NonExistingNode extends AbstractEncryptedNode { throw new UnsupportedOperationException("Resource doesn't exist."); } - @Override - protected void determineProperties() { - // do nothing. - } - @Override public void move(AbstractEncryptedNode destination) throws DavException { throw new UnsupportedOperationException("Resource doesn't exist."); diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java index 608606eb0..cef5f2a4d 100644 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java +++ b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java @@ -8,6 +8,7 @@ ******************************************************************************/ package org.cryptomator.webdav.jackrabbit; +import java.io.IOException; import java.util.Collection; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -16,11 +17,14 @@ import java.util.concurrent.TimeUnit; import javax.servlet.ServletConfig; import javax.servlet.ServletException; +import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.DavLocatorFactory; import org.apache.jackrabbit.webdav.DavResource; import org.apache.jackrabbit.webdav.DavResourceFactory; +import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.DavSessionProvider; import org.apache.jackrabbit.webdav.WebdavRequest; +import org.apache.jackrabbit.webdav.WebdavResponse; import org.apache.jackrabbit.webdav.server.AbstractWebdavServlet; import org.cryptomator.crypto.Cryptor; @@ -67,6 +71,17 @@ public class WebDavServlet extends AbstractWebdavServlet { } } + @Override + protected void doMkCol(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException { + if (resource instanceof EncryptedDirDuringCreation) { + EncryptedDirDuringCreation dir = (EncryptedDirDuringCreation) resource; + dir.doCreate(); + response.setStatus(DavServletResponse.SC_CREATED); + } else { + + } + } + @Override protected boolean isPreconditionValid(WebdavRequest request, DavResource resource) { return !resource.exists() || request.matchesIfHeader(resource); diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java index 2ec02b1fc..03b669549 100644 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java +++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java @@ -15,15 +15,12 @@ import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; import java.nio.charset.StandardCharsets; -import java.nio.file.DirectoryStream.Filter; -import java.nio.file.Path; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; -import java.util.UUID; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -40,10 +37,8 @@ import javax.security.auth.Destroyable; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.NullOutputStream; -import org.apache.commons.lang3.StringUtils; import org.bouncycastle.crypto.generators.SCrypt; import org.cryptomator.crypto.Cryptor; -import org.cryptomator.crypto.CryptorMetadataSupport; import org.cryptomator.crypto.aes256.CounterAwareInputStream.CounterAwareInputLimitReachedException; import org.cryptomator.crypto.exceptions.CounterOverflowException; import org.cryptomator.crypto.exceptions.DecryptFailedException; @@ -55,10 +50,9 @@ import org.cryptomator.crypto.exceptions.WrongPasswordException; import org.cryptomator.crypto.io.SeekableByteChannelInputStream; import org.cryptomator.crypto.io.SeekableByteChannelOutputStream; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, FileNamingConventions { +public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration { /** * Defined in static initializer. Defaults to 256, but falls back to maximum value possible, if JCE Unlimited Strength Jurisdiction Policy Files isn't installed. Those files can be downloaded @@ -296,71 +290,20 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi return encryptedThenHashedPath.substring(0, 2) + nativePathSep + encryptedThenHashedPath.substring(2); } - /** - * Each path component, i.e. file or directory name separated by path separators, gets encrypted for its own.
- * Encryption will blow up the filename length due to aes block sizes, IVs and base32 encoding. The result may be too long for some old file systems.
- * This means that we need a workaround for filenames longer than the limit defined in {@link FileNamingConventions#ENCRYPTED_FILENAME_LENGTH_LIMIT}.
- *
- * In any case we will create the encrypted filename normally. For those, that are too long, we calculate a checksum. No cryptographically secure hash is needed here. We just want an uniform - * distribution for better load balancing. All encrypted filenames with the same checksum will then share a metadata file, in which a lookup map between encrypted filenames and short unique - * alternative names are stored.
- *
- * These alternative names consist of the checksum, a unique id and a special file extension defined in {@link FileNamingConventions#LONG_NAME_FILE_EXT}. - */ @Override - public String encryptFilename(String cleartextName, CryptorMetadataSupport ioSupport) throws IOException { + public String encryptFilename(String cleartextName) { final byte[] cleartextBytes = cleartextName.getBytes(StandardCharsets.UTF_8); - - // encrypt: final byte[] encryptedBytes = AesSivCipherUtil.sivEncrypt(primaryMasterKey, hMacMasterKey, cleartextBytes); - final String ivAndCiphertext = ENCRYPTED_FILENAME_CODEC.encodeAsString(encryptedBytes); - - if (ivAndCiphertext.length() + BASIC_FILE_EXT.length() > ENCRYPTED_FILENAME_LENGTH_LIMIT) { - final String metadataGroup = ivAndCiphertext.substring(0, LONG_NAME_PREFIX_LENGTH); - final LongFilenameMetadata metadata = this.getMetadata(ioSupport, metadataGroup); - final String alternativeFileName = metadataGroup + metadata.getOrCreateUuidForEncryptedFilename(ivAndCiphertext).toString() + LONG_NAME_FILE_EXT; - this.storeMetadata(ioSupport, metadataGroup, metadata); - return alternativeFileName; - } else { - return ivAndCiphertext + BASIC_FILE_EXT; - } + return ENCRYPTED_FILENAME_CODEC.encodeAsString(encryptedBytes); } @Override - public String decryptFilename(String ciphertextName, CryptorMetadataSupport ioSupport) throws DecryptFailedException, IOException { - final String ciphertext; - if (ciphertextName.endsWith(LONG_NAME_FILE_EXT)) { - final String basename = StringUtils.removeEnd(ciphertextName, LONG_NAME_FILE_EXT); - final String metadataGroup = basename.substring(0, LONG_NAME_PREFIX_LENGTH); - final String uuid = basename.substring(LONG_NAME_PREFIX_LENGTH); - final LongFilenameMetadata metadata = this.getMetadata(ioSupport, metadataGroup); - ciphertext = metadata.getEncryptedFilenameForUUID(UUID.fromString(uuid)); - } else if (ciphertextName.endsWith(BASIC_FILE_EXT)) { - ciphertext = StringUtils.removeEndIgnoreCase(ciphertextName, BASIC_FILE_EXT); - } else { - throw new IllegalArgumentException("Unsupported path component: " + ciphertextName); - } - - // decrypt: - final byte[] encryptedBytes = ENCRYPTED_FILENAME_CODEC.decode(ciphertext); + public String decryptFilename(String ciphertextName) throws DecryptFailedException { + final byte[] encryptedBytes = ENCRYPTED_FILENAME_CODEC.decode(ciphertextName); final byte[] cleartextBytes = AesSivCipherUtil.sivDecrypt(primaryMasterKey, hMacMasterKey, encryptedBytes); - return new String(cleartextBytes, StandardCharsets.UTF_8); } - private LongFilenameMetadata getMetadata(CryptorMetadataSupport ioSupport, String metadataGroup) throws IOException { - final byte[] fileContent = ioSupport.readMetadata(metadataGroup); - if (fileContent == null) { - return new LongFilenameMetadata(); - } else { - return objectMapper.readValue(fileContent, LongFilenameMetadata.class); - } - } - - private void storeMetadata(CryptorMetadataSupport ioSupport, String metadataGroup, LongFilenameMetadata metadata) throws JsonProcessingException, IOException { - ioSupport.writeMetadata(metadataGroup, objectMapper.writeValueAsBytes(metadata)); - } - @Override public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException, MacAuthenticationFailedException { // read header: @@ -616,14 +559,4 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi return plaintextSize; } - @Override - public Filter getPayloadFilesFilter() { - return new Filter() { - @Override - public boolean accept(Path entry) throws IOException { - return ENCRYPTED_FILE_MATCHER.matches(entry); - } - }; - } - } diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java index 852248b9b..b31dcfe99 100644 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java +++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java @@ -8,6 +8,9 @@ ******************************************************************************/ package org.cryptomator.crypto.aes256; +import org.apache.commons.codec.binary.Base32; +import org.apache.commons.codec.binary.BaseNCodec; + interface AesCryptographicConfiguration { /** @@ -78,4 +81,9 @@ interface AesCryptographicConfiguration { */ int AES_BLOCK_LENGTH = 16; + /** + * How to encode the encrypted file names safely. Base32 uses only alphanumeric characters and is case-insensitive. + */ + BaseNCodec ENCRYPTED_FILENAME_CODEC = new Base32(); + } diff --git a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java index a04303fb2..ae55c916d 100644 --- a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java +++ b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java @@ -15,13 +15,10 @@ import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.SeekableByteChannel; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; import javax.security.auth.DestroyFailedException; import org.apache.commons.io.IOUtils; -import org.cryptomator.crypto.CryptorMetadataSupport; import org.cryptomator.crypto.exceptions.DecryptFailedException; import org.cryptomator.crypto.exceptions.EncryptFailedException; import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException; @@ -210,7 +207,6 @@ public class Aes256CryptorTest { @Test public void testEncryptionOfFilenames() throws IOException, DecryptFailedException { - final CryptorMetadataSupport ioSupportMock = new CryptoIOSupportMock(); final Aes256Cryptor cryptor = new Aes256Cryptor(); // directory paths @@ -222,35 +218,19 @@ public class Aes256CryptorTest { // long file names final String str50chars = "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee"; final String originalPath2 = str50chars + str50chars + str50chars + str50chars + str50chars + "_isLongerThan255Chars.txt"; - final String encryptedPath2a = cryptor.encryptFilename(originalPath2, ioSupportMock); - final String encryptedPath2b = cryptor.encryptFilename(originalPath2, ioSupportMock); + final String encryptedPath2a = cryptor.encryptFilename(originalPath2); + final String encryptedPath2b = cryptor.encryptFilename(originalPath2); Assert.assertEquals(encryptedPath2a, encryptedPath2b); - final String decryptedPath2 = cryptor.decryptFilename(encryptedPath2a, ioSupportMock); + final String decryptedPath2 = cryptor.decryptFilename(encryptedPath2a); Assert.assertEquals(originalPath2, decryptedPath2); // block size length file names final String originalPath3 = "aaaabbbbccccdddd"; - final String encryptedPath3a = cryptor.encryptFilename(originalPath3, ioSupportMock); - final String encryptedPath3b = cryptor.encryptFilename(originalPath3, ioSupportMock); + final String encryptedPath3a = cryptor.encryptFilename(originalPath3); + final String encryptedPath3b = cryptor.encryptFilename(originalPath3); Assert.assertEquals(encryptedPath3a, encryptedPath3b); - final String decryptedPath3 = cryptor.decryptFilename(encryptedPath3a, ioSupportMock); + final String decryptedPath3 = cryptor.decryptFilename(encryptedPath3a); Assert.assertEquals(originalPath3, decryptedPath3); } - private static class CryptoIOSupportMock implements CryptorMetadataSupport { - - private final Map map = new HashMap<>(); - - @Override - public void writeMetadata(String metadataGroup, byte[] encryptedMetadata) { - map.put(metadataGroup, encryptedMetadata); - } - - @Override - public byte[] readMetadata(String metadataGroup) { - return map.get(metadataGroup); - } - - } - } diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptorDecorator.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptorDecorator.java index ab44886f0..9f91ddac1 100644 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptorDecorator.java +++ b/main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptorDecorator.java @@ -4,8 +4,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.channels.SeekableByteChannel; -import java.nio.file.DirectoryStream.Filter; -import java.nio.file.Path; import javax.security.auth.DestroyFailedException; @@ -40,13 +38,13 @@ public class AbstractCryptorDecorator implements Cryptor { } @Override - public String encryptFilename(String cleartextName, CryptorMetadataSupport ioSupport) throws IOException { - return cryptor.encryptFilename(cleartextName, ioSupport); + public String encryptFilename(String cleartextName) { + return cryptor.encryptFilename(cleartextName); } @Override - public String decryptFilename(String ciphertextName, CryptorMetadataSupport ioSupport) throws IOException, DecryptFailedException { - return cryptor.decryptFilename(ciphertextName, ioSupport); + public String decryptFilename(String ciphertextName) throws DecryptFailedException { + return cryptor.decryptFilename(ciphertextName); } @Override @@ -74,11 +72,6 @@ public class AbstractCryptorDecorator implements Cryptor { return cryptor.encryptFile(plaintextFile, encryptedFile); } - @Override - public Filter getPayloadFilesFilter() { - return cryptor.getPayloadFilesFilter(); - } - @Override public void destroy() throws DestroyFailedException { cryptor.destroy(); diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java index 55c637e5d..c2c479f5e 100644 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java +++ b/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java @@ -12,8 +12,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.channels.SeekableByteChannel; -import java.nio.file.DirectoryStream.Filter; -import java.nio.file.Path; import javax.security.auth.Destroyable; @@ -57,22 +55,18 @@ public interface Cryptor extends Destroyable { * Encrypts the name of a file. See {@link #encryptDirectoryPath(String, char)} for parent dir. * * @param cleartextName A plaintext filename without any preceeding directory paths. - * @param ioSupport Support object allowing the Cryptor to read and write its own metadata to a storage space associated with this support object. * @return Encrypted filename. - * @throws IOException If ioSupport throws an IOException */ - String encryptFilename(String cleartextName, CryptorMetadataSupport ioSupport) throws IOException; + String encryptFilename(String cleartextName); /** * Decrypts the name of a file. * * @param ciphertextName A ciphertext filename without any preceeding directory paths. - * @param ioSupport Support object allowing the Cryptor to read and write its own metadata to a storage space associated with this support object. * @return Decrypted filename. * @throws DecryptFailedException If the decryption failed for various reasons (including wrong password). - * @throws IOException If ioSupport throws an IOException */ - String decryptFilename(String ciphertextName, CryptorMetadataSupport ioSupport) throws IOException, DecryptFailedException; + String decryptFilename(String ciphertextName) throws DecryptFailedException; /** * @param metadataSupport Support object allowing the Cryptor to read and write its own metadata to the location of the encrypted file. @@ -105,9 +99,4 @@ public interface Cryptor extends Destroyable { */ Long encryptFile(InputStream plaintextFile, SeekableByteChannel encryptedFile) throws IOException, EncryptFailedException; - /** - * @return A filter, that returns true for encrypted files, i.e. if the file is an actual user payload and not a supporting metadata file of the {@link Cryptor}. - */ - Filter getPayloadFilesFilter(); - } diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/CryptorMetadataSupport.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/CryptorMetadataSupport.java deleted file mode 100644 index c3564fb32..000000000 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/CryptorMetadataSupport.java +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 Sebastian Stenzel - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - * - * Contributors: - * Sebastian Stenzel - initial API and implementation - ******************************************************************************/ -package org.cryptomator.crypto; - -import java.io.IOException; - -/** - * Methods that may be called by the Cryptor when accessing a path. - */ -public interface CryptorMetadataSupport { - - /** - * Persists encryptedMetadata in a metadata group. - * - * @param metadataFilename File relative to - * @throws IOException - */ - void writeMetadata(String metadataGroup, byte[] encryptedMetadata) throws IOException; - - /** - * @return Previously written metadata stored in the given metadata group or null if no such group exists. - */ - byte[] readMetadata(String metadataGroup) throws IOException; - -} \ No newline at end of file diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/PathCachingCryptorDecorator.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/PathCachingCryptorDecorator.java index ba955a799..d22c350db 100644 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/PathCachingCryptorDecorator.java +++ b/main/crypto-api/src/main/java/org/cryptomator/crypto/PathCachingCryptorDecorator.java @@ -1,6 +1,5 @@ package org.cryptomator.crypto; -import java.io.IOException; import java.util.Map; import org.apache.commons.collections4.BidiMap; @@ -38,22 +37,22 @@ public class PathCachingCryptorDecorator extends AbstractCryptorDecorator { } @Override - public String encryptFilename(String cleartextName, CryptorMetadataSupport ioSupport) throws IOException { + public String encryptFilename(String cleartextName) { if (nameCache.containsKey(cleartextName)) { return nameCache.get(cleartextName); } else { - final String ciphertextName = cryptor.encryptFilename(cleartextName, ioSupport); + final String ciphertextName = cryptor.encryptFilename(cleartextName); nameCache.put(cleartextName, ciphertextName); return ciphertextName; } } @Override - public String decryptFilename(String ciphertextName, CryptorMetadataSupport ioSupport) throws IOException, DecryptFailedException { + public String decryptFilename(String ciphertextName) throws DecryptFailedException { if (nameCache.containsValue(ciphertextName)) { return nameCache.getKey(ciphertextName); } else { - final String cleartextName = cryptor.decryptFilename(ciphertextName, ioSupport); + final String cleartextName = cryptor.decryptFilename(ciphertextName); nameCache.put(cleartextName, ciphertextName); return ciphertextName; }