Sebastian Stenzel 9 лет назад
Родитель
Сommit
61f1afba87
59 измененных файлов с 0 добавлено и 5540 удалено
  1. 0 1
      main/core/.gitignore
  2. 0 92
      main/core/pom.xml
  3. 0 174
      main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java
  4. 0 31
      main/core/src/main/java/org/cryptomator/webdav/WindowsSucksServlet.java
  5. 0 31
      main/core/src/main/java/org/cryptomator/webdav/exceptions/DavRuntimeException.java
  6. 0 23
      main/core/src/main/java/org/cryptomator/webdav/exceptions/DecryptFailedRuntimeException.java
  7. 0 296
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java
  8. 0 127
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CleartextLocatorFactory.java
  9. 0 287
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoResourceFactory.java
  10. 0 26
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoWarningHandler.java
  11. 0 45
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionImpl.java
  12. 0 36
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionProviderImpl.java
  13. 0 418
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java
  14. 0 154
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java
  15. 0 90
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFilePart.java
  16. 0 108
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileConstants.java
  17. 0 34
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileTimeUtils.java
  18. 0 234
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java
  19. 0 20
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/HttpHeaderProperty.java
  20. 0 104
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java
  21. 0 56
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/SilentlyFailingFileLock.java
  22. 0 118
      main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java
  23. 0 103
      main/core/src/test/java/org/cryptomator/webdav/jackrabbit/CryptorMock.java
  24. 0 272
      main/core/src/test/java/org/cryptomator/webdav/jackrabbit/RangeRequestTest.java
  25. 0 33
      main/core/src/test/resources/log4j2.xml
  26. 0 95
      main/crypto-aes/pom.xml
  27. 0 798
      main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java
  28. 0 89
      main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java
  29. 0 22
      main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/BlocksData.java
  30. 0 15
      main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoComponent.java
  31. 0 29
      main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoModule.java
  32. 0 68
      main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoWorker.java
  33. 0 167
      main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoWorkerExecutor.java
  34. 0 75
      main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/DecryptWorker.java
  35. 0 61
      main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/EncryptWorker.java
  36. 0 77
      main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/KeyFile.java
  37. 0 47
      main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/LengthLimitingOutputStream.java
  38. 0 128
      main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/LengthObfuscatingInputStream.java
  39. 0 207
      main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java
  40. 0 79
      main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/ByteBufferBackedSeekableChannel.java
  41. 0 15
      main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/CryptoTestComponent.java
  42. 0 25
      main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/CryptoTestModule.java
  43. 0 23
      main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/InsecureRandomMock.java
  44. 0 33
      main/crypto-aes/src/test/resources/log4j2.xml
  45. 0 1
      main/crypto-api/.gitignore
  46. 0 50
      main/crypto-api/pom.xml
  47. 0 85
      main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptorDecorator.java
  48. 0 102
      main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java
  49. 0 26
      main/crypto-api/src/main/java/org/cryptomator/crypto/CryptorIOSampling.java
  50. 0 65
      main/crypto-api/src/main/java/org/cryptomator/crypto/PathCachingCryptorDecorator.java
  51. 0 118
      main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingCryptorDecorator.java
  52. 0 15
      main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/CryptingException.java
  53. 0 13
      main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java
  54. 0 13
      main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/EncryptFailedException.java
  55. 0 11
      main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/MacAuthenticationFailedException.java
  56. 0 11
      main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/MasterkeyDecryptionException.java
  57. 0 23
      main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedKeyLengthException.java
  58. 0 32
      main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedVaultException.java
  59. 0 9
      main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/WrongPasswordException.java

+ 0 - 1
main/core/.gitignore

@@ -1 +0,0 @@
-/target/

+ 0 - 92
main/core/pom.xml

@@ -1,92 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  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
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<parent>
-		<groupId>org.cryptomator</groupId>
-		<artifactId>main</artifactId>
-		<version>0.11.0-SNAPSHOT</version>
-	</parent>
-	<artifactId>core</artifactId>
-	<name>Cryptomator WebDAV and I/O module</name>
-
-	<properties>
-		<jetty.version>9.3.3.v20150827</jetty.version>
-		<jackrabbit.version>2.11.0</jackrabbit.version>
-	</properties>
-
-	<dependencies>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>crypto-api</artifactId>
-		</dependency>
-
-		<!-- Jetty (Servlet Container) -->
-		<dependency>
-			<groupId>org.eclipse.jetty</groupId>
-			<artifactId>jetty-server</artifactId>
-			<version>${jetty.version}</version>
-		</dependency>
-		<dependency>
-			<groupId>org.eclipse.jetty</groupId>
-			<artifactId>jetty-webapp</artifactId>
-			<version>${jetty.version}</version>
-		</dependency>
-		<dependency>
-			<groupId>commons-httpclient</groupId>
-			<artifactId>commons-httpclient</artifactId>
-			<scope>test</scope>
-		</dependency>
-
-		<!-- Jackrabbit -->
-		<dependency>
-			<groupId>org.apache.jackrabbit</groupId>
-			<artifactId>jackrabbit-webdav</artifactId>
-			<version>${jackrabbit.version}</version>
-		</dependency>
-
-		<!-- Guava -->
-		<dependency>
-			<groupId>com.google.guava</groupId>
-			<artifactId>guava</artifactId>
-		</dependency>
-
-		<!-- I/O -->
-		<dependency>
-			<groupId>commons-io</groupId>
-			<artifactId>commons-io</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.apache.commons</groupId>
-			<artifactId>commons-lang3</artifactId>
-		</dependency>
-
-		<!-- JSON -->
-		<dependency>
-			<groupId>com.fasterxml.jackson.core</groupId>
-			<artifactId>jackson-databind</artifactId>
-		</dependency>
-		
-		<!-- Tests -->
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>commons-test</artifactId>
-		</dependency>
-	</dependencies>
-
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.jacoco</groupId>
-				<artifactId>jacoco-maven-plugin</artifactId>
-			</plugin>
-		</plugins>
-	</build>
-</project>

+ 0 - 174
main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java

@@ -1,174 +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.webdav;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.UUID;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.SystemUtils;
-import org.cryptomator.crypto.Cryptor;
-import org.cryptomator.webdav.jackrabbit.WebDavServlet;
-import org.eclipse.jetty.server.Connector;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.server.handler.ContextHandlerCollection;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.util.component.LifeCycle;
-import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.eclipse.jetty.util.thread.ThreadPool;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public final class WebDavServer {
-
-	private static final Logger LOG = LoggerFactory.getLogger(WebDavServer.class);
-	private static final String LOCALHOST = SystemUtils.IS_OS_WINDOWS ? "::1" : "localhost";
-	private static final int MAX_PENDING_REQUESTS = 200;
-	private static final int MAX_THREADS = 200;
-	private static final int MIN_THREADS = 4;
-	private static final int THREAD_IDLE_SECONDS = 20;
-	private final Server server;
-	private final ServerConnector localConnector;
-	private final ContextHandlerCollection servletCollection;
-
-	public WebDavServer() {
-		final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(MAX_PENDING_REQUESTS);
-		final ThreadPool tp = new QueuedThreadPool(MAX_THREADS, MIN_THREADS, THREAD_IDLE_SECONDS, queue);
-		server = new Server(tp);
-		localConnector = new ServerConnector(server);
-		localConnector.setHost(LOCALHOST);
-		servletCollection = new ContextHandlerCollection();
-
-		if (SystemUtils.IS_OS_WINDOWS) {
-			final ServletContextHandler servletContext = new ServletContextHandler(servletCollection, "/", ServletContextHandler.NO_SESSIONS);
-			final ServletHolder servlet = new ServletHolder(WindowsSucksServlet.class);
-			servletContext.addServlet(servlet, "/");
-		}
-
-		server.setConnectors(new Connector[] {localConnector});
-		server.setHandler(servletCollection);
-	}
-
-	public synchronized void start() {
-		try {
-			server.start();
-			LOG.info("Cryptomator is running on port {}", getPort());
-		} catch (Exception ex) {
-			throw new RuntimeException("Server couldn't be started", ex);
-		}
-	}
-
-	public boolean isRunning() {
-		return server.isRunning();
-	}
-
-	public synchronized void stop() {
-		try {
-			server.stop();
-		} catch (Exception ex) {
-			LOG.error("Server couldn't be stopped", ex);
-		}
-	}
-
-	/**
-	 * @param workDir Path of encrypted folder.
-	 * @param cryptor A fully initialized cryptor instance ready to en- or decrypt streams.
-	 * @param failingMacCollection A (observable, thread-safe) collection, to which the names of resources are written, whose MAC authentication fails.
-	 * @param name The name of the folder. Must be non-empty and only contain any of _ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
-	 * @return servlet
-	 */
-	public ServletLifeCycleAdapter createServlet(final Path workDir, final Cryptor cryptor, final Collection<String> failingMacCollection, final Collection<String> whitelistedResourceCollection, final String name) {
-		try {
-			if (StringUtils.isEmpty(name)) {
-				throw new IllegalArgumentException("name empty");
-			}
-			if (!StringUtils.containsOnly(name, "_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")) {
-				throw new IllegalArgumentException("name contains illegal characters: " + name);
-			}
-			final URI uri = new URI(null, null, localConnector.getHost(), localConnector.getLocalPort(), "/" + UUID.randomUUID().toString() + "/" + name, null, null);
-
-			final ServletContextHandler servletContext = new ServletContextHandler(servletCollection, uri.getRawPath(), ServletContextHandler.SESSIONS);
-			final ServletHolder servlet = getWebDavServletHolder(workDir.toString(), cryptor, failingMacCollection, whitelistedResourceCollection);
-			servletContext.addServlet(servlet, "/*");
-
-			servletCollection.mapContexts();
-
-			LOG.debug("{} available on http:{}", workDir, uri.getRawSchemeSpecificPart());
-			return new ServletLifeCycleAdapter(servletContext, uri);
-		} catch (URISyntaxException e) {
-			throw new IllegalStateException("Invalid hard-coded URI components.", e);
-		}
-	}
-
-	private ServletHolder getWebDavServletHolder(final String workDir, final Cryptor cryptor, final Collection<String> failingMacCollection, final Collection<String> whitelistedResourceCollection) {
-		final ServletHolder result = new ServletHolder("Cryptomator-WebDAV-Servlet", new WebDavServlet(cryptor, failingMacCollection, whitelistedResourceCollection));
-		result.setInitParameter(WebDavServlet.CFG_FS_ROOT, workDir);
-		return result;
-	}
-
-	public int getPort() {
-		return localConnector.getLocalPort();
-	}
-
-	/**
-	 * Exposes implementation-specific methods to other modules.
-	 */
-	public class ServletLifeCycleAdapter implements AutoCloseable {
-
-		private final LifeCycle lifecycle;
-		private final URI servletUri;
-
-		private ServletLifeCycleAdapter(LifeCycle lifecycle, URI servletUri) {
-			this.lifecycle = lifecycle;
-			this.servletUri = servletUri;
-		}
-
-		public boolean isRunning() {
-			return lifecycle.isRunning();
-		}
-
-		public boolean start() {
-			try {
-				lifecycle.start();
-				return true;
-			} catch (Exception e) {
-				LOG.error("Failed to start", e);
-				return false;
-			}
-		}
-
-		public boolean stop() {
-			try {
-				lifecycle.stop();
-				return true;
-			} catch (Exception e) {
-				LOG.error("Failed to stop", e);
-				return false;
-			}
-		}
-
-		public URI getServletUri() {
-			return servletUri;
-		}
-
-		@Override
-		public void close() throws Exception {
-			this.stop();
-		}
-
-	}
-
-}

+ 0 - 31
main/core/src/main/java/org/cryptomator/webdav/WindowsSucksServlet.java

@@ -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.webdav;
-
-import java.io.IOException;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-/**
- * Windows mount attempts will fail, if not all requests on parent paths of a WebDAV resource get served. This servlet will respond to any
- * request with status code 200, if the requested resource doesn't match a different servlet.
- */
-public class WindowsSucksServlet extends HttpServlet {
-
-	private static final long serialVersionUID = -515280795196074354L;
-
-	@Override
-	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-		resp.setStatus(HttpServletResponse.SC_OK);
-	}
-
-}

+ 0 - 31
main/core/src/main/java/org/cryptomator/webdav/exceptions/DavRuntimeException.java

@@ -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.webdav.exceptions;
-
-import org.apache.jackrabbit.webdav.DavException;
-
-public class DavRuntimeException extends RuntimeException {
-
-	private static final long serialVersionUID = -4713080133052143303L;
-
-	public DavRuntimeException(DavException davException) {
-		super(davException);
-	}
-
-	@Override
-	public String getMessage() {
-		return getCause().getMessage();
-	}
-
-	@Override
-	public String getLocalizedMessage() {
-		return getCause().getLocalizedMessage();
-	}
-
-}

+ 0 - 23
main/core/src/main/java/org/cryptomator/webdav/exceptions/DecryptFailedRuntimeException.java

@@ -1,23 +0,0 @@
-package org.cryptomator.webdav.exceptions;
-
-import org.cryptomator.crypto.exceptions.DecryptFailedException;
-
-public class DecryptFailedRuntimeException extends RuntimeException {
-
-	private static final long serialVersionUID = -2726689824823439865L;
-
-	public DecryptFailedRuntimeException(DecryptFailedException cause) {
-		super(cause);
-	}
-
-	@Override
-	public String getMessage() {
-		return getCause().getMessage();
-	}
-
-	@Override
-	public String getLocalizedMessage() {
-		return getCause().getLocalizedMessage();
-	}
-
-}

+ 0 - 296
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java

@@ -1,296 +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.webdav.jackrabbit;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.nio.file.attribute.BasicFileAttributeView;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.FileTime;
-import java.util.Arrays;
-import java.util.List;
-
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.jackrabbit.webdav.DavException;
-import org.apache.jackrabbit.webdav.DavResource;
-import org.apache.jackrabbit.webdav.DavResourceLocator;
-import org.apache.jackrabbit.webdav.DavServletResponse;
-import org.apache.jackrabbit.webdav.DavSession;
-import org.apache.jackrabbit.webdav.MultiStatusResponse;
-import org.apache.jackrabbit.webdav.lock.ActiveLock;
-import org.apache.jackrabbit.webdav.lock.LockInfo;
-import org.apache.jackrabbit.webdav.lock.LockManager;
-import org.apache.jackrabbit.webdav.lock.Scope;
-import org.apache.jackrabbit.webdav.lock.Type;
-import org.apache.jackrabbit.webdav.property.DavProperty;
-import org.apache.jackrabbit.webdav.property.DavPropertyName;
-import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
-import org.apache.jackrabbit.webdav.property.DavPropertySet;
-import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
-import org.apache.jackrabbit.webdav.property.PropEntry;
-import org.cryptomator.crypto.Cryptor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-abstract class AbstractEncryptedNode implements DavResource {
-
-	private static final Logger LOG = LoggerFactory.getLogger(AbstractEncryptedNode.class);
-	private static final String DAV_COMPLIANCE_CLASSES = "1, 2";
-	private static final String[] DAV_CREATIONDATE_PROPNAMES = {DavPropertyName.CREATIONDATE.getName(), "Win32CreationTime"};
-	private static final String[] DAV_MODIFIEDDATE_PROPNAMES = {DavPropertyName.GETLASTMODIFIED.getName(), "Win32LastModifiedTime"};
-
-	protected final CryptoResourceFactory factory;
-	protected final DavResourceLocator locator;
-	protected final DavSession session;
-	protected final LockManager lockManager;
-	protected final Cryptor cryptor;
-	protected final Path filePath;
-	protected final DavPropertySet properties;
-
-	protected AbstractEncryptedNode(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, Path filePath) {
-		this.factory = factory;
-		this.locator = locator;
-		this.session = session;
-		this.lockManager = lockManager;
-		this.cryptor = cryptor;
-		this.filePath = filePath;
-		this.properties = new DavPropertySet();
-		if (filePath != null && Files.exists(filePath)) {
-			try {
-				final BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);
-				properties.add(new DefaultDavProperty<String>(DavPropertyName.CREATIONDATE, FileTimeUtils.toRfc1123String(attrs.creationTime())));
-				properties.add(new DefaultDavProperty<String>(DavPropertyName.GETLASTMODIFIED, FileTimeUtils.toRfc1123String(attrs.lastModifiedTime())));
-			} catch (IOException e) {
-				LOG.error("Error determining metadata " + filePath.toString(), e);
-			}
-		}
-	}
-
-	@Override
-	public String getComplianceClass() {
-		return DAV_COMPLIANCE_CLASSES;
-	}
-
-	@Override
-	public String getSupportedMethods() {
-		return METHODS;
-	}
-
-	@Override
-	public boolean exists() {
-		return Files.exists(filePath);
-	}
-
-	@Override
-	public String getDisplayName() {
-		final String resourcePath = getResourcePath();
-		final int lastSlash = resourcePath.lastIndexOf('/');
-		if (lastSlash == -1) {
-			return resourcePath;
-		} else {
-			return resourcePath.substring(lastSlash);
-		}
-	}
-
-	@Override
-	public DavResourceLocator getLocator() {
-		return locator;
-	}
-
-	@Override
-	public String getResourcePath() {
-		return locator.getResourcePath();
-	}
-
-	@Override
-	public String getHref() {
-		return locator.getHref(this.isCollection());
-	}
-
-	@Override
-	public long getModificationTime() {
-		try {
-			return Files.getLastModifiedTime(filePath).toMillis();
-		} catch (IOException e) {
-			return -1;
-		}
-	}
-
-	@Override
-	public DavPropertyName[] getPropertyNames() {
-		return getProperties().getPropertyNames();
-	}
-
-	@Override
-	public DavProperty<?> getProperty(DavPropertyName name) {
-		return getProperties().get(name);
-	}
-
-	@Override
-	public DavPropertySet getProperties() {
-		return properties;
-	}
-
-	@Override
-	public void setProperty(DavProperty<?> property) throws DavException {
-		getProperties().add(property);
-
-		LOG.trace("Set property {}", property.getName());
-		
-		final String namespacelessPropertyName = property.getName().getName();
-		if (Files.exists(filePath)) {
-			try {
-				if (Arrays.asList(DAV_CREATIONDATE_PROPNAMES).contains(namespacelessPropertyName) && property.getValue() instanceof String) {
-					final String createDateStr = (String) property.getValue();
-					final FileTime createTime = FileTimeUtils.fromRfc1123String(createDateStr);
-					final BasicFileAttributeView attrView = Files.getFileAttributeView(filePath, BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);
-					attrView.setTimes(null, null, createTime);
-					LOG.debug("Updating Creation Date: {}", createTime.toString());
-				} else if (Arrays.asList(DAV_MODIFIEDDATE_PROPNAMES).contains(namespacelessPropertyName) && property.getValue() instanceof String) {
-					final String lastModifiedTimeStr = (String) property.getValue();
-					final FileTime lastModifiedTime = FileTimeUtils.fromRfc1123String(lastModifiedTimeStr);
-					final BasicFileAttributeView attrView = Files.getFileAttributeView(filePath, BasicFileAttributeView.class, LinkOption.NOFOLLOW_LINKS);
-					attrView.setTimes(lastModifiedTime, null, null);
-					LOG.debug("Updating Last Modified Date: {}", lastModifiedTime.toString());
-				}
-			} catch (IOException e) {
-				throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR);
-			}
-		}
-	}
-
-	@Override
-	public void removeProperty(DavPropertyName propertyName) throws DavException {
-		getProperties().remove(propertyName);
-	}
-
-	@Override
-	public MultiStatusResponse alterProperties(List<? extends PropEntry> changeList) throws DavException {
-		final DavPropertyNameSet names = new DavPropertyNameSet();
-		for (final PropEntry entry : changeList) {
-			if (entry instanceof DavProperty) {
-				final DavProperty<?> prop = (DavProperty<?>) entry;
-				this.setProperty(prop);
-				names.add(prop.getName());
-			} else if (entry instanceof DavPropertyName) {
-				final DavPropertyName name = (DavPropertyName) entry;
-				this.removeProperty(name);
-				names.add(name);
-			}
-		}
-		return new MultiStatusResponse(this, names);
-	}
-
-	@Override
-	public DavResource getCollection() {
-		if (locator.isRootLocation()) {
-			return null;
-		}
-
-		final String parentResource = StringUtils.prependIfMissing(FilenameUtils.getPathNoEndSeparator(locator.getResourcePath()), "/");
-		final DavResourceLocator parentLocator = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), parentResource);
-		try {
-			return getFactory().createResource(parentLocator, session);
-		} catch (DavException e) {
-			throw new IllegalStateException("Unable to get parent resource with path " + parentLocator.getResourcePath(), e);
-		}
-	}
-
-	@Override
-	public final void move(DavResource dest) throws DavException {
-		if (dest instanceof AbstractEncryptedNode) {
-			try {
-				this.move((AbstractEncryptedNode) dest);
-			} catch (IOException e) {
-				LOG.error("Error moving file from " + this.getResourcePath() + " to " + dest.getResourcePath());
-				throw new UncheckedIOException(e);
-			}
-		} else {
-			throw new IllegalArgumentException("Unsupported resource type: " + dest.getClass().getName());
-		}
-	}
-
-	public abstract void move(AbstractEncryptedNode dest) throws DavException, IOException;
-
-	@Override
-	public final void copy(DavResource dest, boolean shallow) throws DavException {
-		if (dest instanceof AbstractEncryptedNode) {
-			try {
-				this.copy((AbstractEncryptedNode) dest, shallow);
-			} catch (IOException e) {
-				LOG.error("Error copying file from " + this.getResourcePath() + " to " + dest.getResourcePath());
-				throw new UncheckedIOException(e);
-			}
-		} else {
-			throw new IllegalArgumentException("Unsupported resource type: " + dest.getClass().getName());
-		}
-	}
-
-	public abstract void copy(AbstractEncryptedNode dest, boolean shallow) throws DavException, IOException;
-
-	@Override
-	public boolean isLockable(Type type, Scope scope) {
-		return true;
-	}
-
-	@Override
-	public boolean hasLock(Type type, Scope scope) {
-		return lockManager.getLock(type, scope, this) != null;
-	}
-
-	@Override
-	public ActiveLock getLock(Type type, Scope scope) {
-		return lockManager.getLock(type, scope, this);
-	}
-
-	@Override
-	public ActiveLock[] getLocks() {
-		final ActiveLock exclusiveWriteLock = getLock(Type.WRITE, Scope.EXCLUSIVE);
-		if (exclusiveWriteLock != null) {
-			return new ActiveLock[] {exclusiveWriteLock};
-		} else {
-			return new ActiveLock[0];
-		}
-	}
-
-	@Override
-	public ActiveLock lock(LockInfo reqLockInfo) throws DavException {
-		return lockManager.createLock(reqLockInfo, this);
-	}
-
-	@Override
-	public ActiveLock refreshLock(LockInfo reqLockInfo, String lockToken) throws DavException {
-		return lockManager.refreshLock(reqLockInfo, lockToken, this);
-	}
-
-	@Override
-	public void unlock(String lockToken) throws DavException {
-		lockManager.releaseLock(lockToken, this);
-	}
-
-	@Override
-	public void addLockManager(LockManager lockmgr) {
-		throw new UnsupportedOperationException("Locks are managed");
-	}
-
-	@Override
-	public CryptoResourceFactory getFactory() {
-		return factory;
-	}
-
-	@Override
-	public DavSession getSession() {
-		return session;
-	}
-
-}

+ 0 - 127
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CleartextLocatorFactory.java

@@ -1,127 +0,0 @@
-package org.cryptomator.webdav.jackrabbit;
-
-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;
-
-public class CleartextLocatorFactory implements DavLocatorFactory {
-
-	private final String pathPrefix;
-
-	public CleartextLocatorFactory(String pathPrefix) {
-		this.pathPrefix = StringUtils.removeEnd(pathPrefix, "/");
-	}
-
-	// resourcePath == repositoryPath. No encryption here.
-
-	@Override
-	public DavResourceLocator createResourceLocator(String prefix, String href) {
-		final String fullPrefix = StringUtils.removeEnd(prefix, "/");
-		final String relativeHref = StringUtils.removeStart(href, fullPrefix);
-
-		final String relativeCleartextPath = EncodeUtil.unescape(relativeHref);
-		assert relativeCleartextPath.startsWith("/");
-		return new CleartextLocator(relativeCleartextPath);
-	}
-
-	@Override
-	public DavResourceLocator createResourceLocator(String prefix, String workspacePath, String resourcePath) {
-		assert resourcePath.startsWith("/");
-		return new CleartextLocator(resourcePath);
-	}
-
-	@Override
-	public DavResourceLocator createResourceLocator(String prefix, String workspacePath, String path, boolean isResourcePath) {
-		assert path.startsWith("/");
-		return new CleartextLocator(path);
-	}
-
-	private class CleartextLocator implements DavResourceLocator {
-
-		private final String relativeCleartextPath;
-
-		private CleartextLocator(String relativeCleartextPath) {
-			this.relativeCleartextPath = StringUtils.prependIfMissing(FilenameUtils.normalizeNoEndSeparator(relativeCleartextPath, true), "/");
-		}
-
-		@Override
-		public String getPrefix() {
-			return pathPrefix;
-		}
-
-		@Override
-		public String getResourcePath() {
-			return relativeCleartextPath;
-		}
-
-		@Override
-		public String getWorkspacePath() {
-			return null;
-		}
-
-		@Override
-		public String getWorkspaceName() {
-			return null;
-		}
-
-		@Override
-		public boolean isSameWorkspace(DavResourceLocator locator) {
-			return false;
-		}
-
-		@Override
-		public boolean isSameWorkspace(String workspaceName) {
-			return false;
-		}
-
-		@Override
-		public String getHref(boolean isCollection) {
-			final String encodedResourcePath = EncodeUtil.escapePath(relativeCleartextPath);
-			if (isRootLocation()) {
-				return pathPrefix + "/";
-			} else if (isCollection) {
-				return pathPrefix + encodedResourcePath + "/";
-			} else {
-				return pathPrefix + encodedResourcePath;
-			}
-		}
-
-		@Override
-		public boolean isRootLocation() {
-			return "/".equals(relativeCleartextPath);
-		}
-
-		@Override
-		public DavLocatorFactory getFactory() {
-			return CleartextLocatorFactory.this;
-		}
-
-		@Override
-		public String getRepositoryPath() {
-			return relativeCleartextPath;
-		}
-
-		@Override
-		public String toString() {
-			return "Locator: " + relativeCleartextPath + " (Prefix: " + pathPrefix + ")";
-		}
-
-		@Override
-		public int hashCode() {
-			return relativeCleartextPath.hashCode();
-		}
-
-		@Override
-		public boolean equals(Object obj) {
-			if (obj instanceof CleartextLocator) {
-				final CleartextLocator other = (CleartextLocator) obj;
-				return relativeCleartextPath == null && other.relativeCleartextPath == null || relativeCleartextPath.equals(other.relativeCleartextPath);
-			} else {
-				return false;
-			}
-		}
-	}
-
-}

+ 0 - 287
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoResourceFactory.java

@@ -1,287 +0,0 @@
-package org.cryptomator.webdav.jackrabbit;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.attribute.FileTime;
-import java.time.format.DateTimeParseException;
-
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.ImmutablePair;
-import org.apache.commons.lang3.tuple.Pair;
-import org.apache.jackrabbit.webdav.DavException;
-import org.apache.jackrabbit.webdav.DavMethods;
-import org.apache.jackrabbit.webdav.DavResource;
-import org.apache.jackrabbit.webdav.DavResourceFactory;
-import org.apache.jackrabbit.webdav.DavResourceLocator;
-import org.apache.jackrabbit.webdav.DavServletRequest;
-import org.apache.jackrabbit.webdav.DavServletResponse;
-import org.apache.jackrabbit.webdav.DavSession;
-import org.apache.jackrabbit.webdav.lock.LockManager;
-import org.apache.jackrabbit.webdav.lock.SimpleLockManager;
-import org.cryptomator.crypto.Cryptor;
-import org.eclipse.jetty.http.HttpHeader;
-
-public class CryptoResourceFactory implements DavResourceFactory, FileConstants {
-
-	private static final String RANGE_BYTE_PREFIX = "bytes=";
-	private static final char RANGE_SET_SEP = ',';
-	private static final char RANGE_SEP = '-';
-
-	private final LockManager lockManager = new SimpleLockManager();
-	private final Cryptor cryptor;
-	private final CryptoWarningHandler cryptoWarningHandler;
-	private final Path dataRoot;
-	private final FilenameTranslator filenameTranslator;
-
-	CryptoResourceFactory(Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler, String vaultRoot) {
-		Path vaultRootPath = FileSystems.getDefault().getPath(vaultRoot);
-		this.cryptor = cryptor;
-		this.cryptoWarningHandler = cryptoWarningHandler;
-		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 (locator.isRootLocation()) {
-			return createRootDirectory(locator, request.getDavSession());
-		}
-
-		try {
-			final Path filePath = getEncryptedFilePath(locator.getResourcePath(), false);
-			final Path dirFilePath = getEncryptedDirectoryFilePath(locator.getResourcePath(), false);
-			final String rangeHeader = request.getHeader(HttpHeader.RANGE.asString());
-			final String ifRangeHeader = request.getHeader(HttpHeader.IF_RANGE.asString());
-			if (Files.exists(dirFilePath) || DavMethods.METHOD_MKCOL.equals(request.getMethod())) {
-				// DIRECTORY
-				return createDirectory(locator, request.getDavSession(), dirFilePath);
-			} else if (Files.exists(filePath) && DavMethods.METHOD_GET.equals(request.getMethod()) && rangeHeader != null && isRangeSatisfiable(rangeHeader) && isIfRangePreconditionFulfilled(ifRangeHeader, filePath)) {
-				// FILE RANGE
-				final Pair<String, String> requestRange = getRequestRange(rangeHeader);
-				response.setStatus(DavServletResponse.SC_PARTIAL_CONTENT);
-				return createFilePart(locator, request.getDavSession(), requestRange, filePath);
-			} else if (Files.exists(filePath) && DavMethods.METHOD_GET.equals(request.getMethod()) && rangeHeader != null && isRangeSatisfiable(rangeHeader) && !isIfRangePreconditionFulfilled(ifRangeHeader, filePath)) {
-				// FULL FILE (if-range not fulfilled)
-				return createFile(locator, request.getDavSession(), filePath);
-			} else if (Files.exists(filePath) && DavMethods.METHOD_GET.equals(request.getMethod()) && rangeHeader != null && !isRangeSatisfiable(rangeHeader)) {
-				// FULL FILE (unsatisfiable range)
-				response.setStatus(DavServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
-				final EncryptedFile file = createFile(locator, request.getDavSession(), filePath);
-				response.addHeader(HttpHeader.CONTENT_RANGE.asString(), "bytes */" + file.getContentLength());
-				return file;
-			} else if (Files.exists(filePath) || DavMethods.METHOD_PUT.equals(request.getMethod())) {
-				// FULL FILE (as requested)
-				return createFile(locator, request.getDavSession(), filePath);
-			}
-		} catch (NonExistingParentException e) {
-			// return non-existing
-		}
-		return createNonExisting(locator, request.getDavSession());
-	}
-
-	@Override
-	public final DavResource createResource(DavResourceLocator locator, DavSession session) throws DavException {
-		if (locator.isRootLocation()) {
-			return createRootDirectory(locator, session);
-		}
-
-		try {
-			final Path filePath = getEncryptedFilePath(locator.getResourcePath(), false);
-			final Path dirFilePath = getEncryptedDirectoryFilePath(locator.getResourcePath(), false);
-			if (Files.exists(dirFilePath)) {
-				return createDirectory(locator, session, dirFilePath);
-			} else if (Files.exists(filePath)) {
-				return createFile(locator, session, filePath);
-			}
-		} catch (NonExistingParentException e) {
-			// return non-existing
-		}
-		return createNonExisting(locator, session);
-	}
-
-	DavResource createChildDirectoryResource(DavResourceLocator locator, DavSession session, Path existingDirectoryFile) throws DavException {
-		return createDirectory(locator, session, existingDirectoryFile);
-	}
-
-	DavResource createChildFileResource(DavResourceLocator locator, DavSession session, Path existingFile) throws DavException {
-		return createFile(locator, session, existingFile);
-	}
-
-	/**
-	 * @return <code>true</code> if a partial response should be generated according to an If-Range precondition.
-	 */
-	private boolean isIfRangePreconditionFulfilled(String ifRangeHeader, Path filePath) throws DavException {
-		if (ifRangeHeader == null) {
-			// no header set -> fulfilled implicitly
-			return true;
-		} else {
-			try {
-				final FileTime expectedTime = FileTimeUtils.fromRfc1123String(ifRangeHeader);
-				final FileTime actualTime = Files.getLastModifiedTime(filePath);
-				return expectedTime.compareTo(actualTime) == 0;
-			} catch (DateTimeParseException e) {
-				throw new DavException(DavServletResponse.SC_BAD_REQUEST, "Unsupported If-Range header: " + ifRangeHeader);
-			} catch (IOException e) {
-				throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR, e);
-			}
-		}
-	}
-
-	/**
-	 * @return <code>true</code> if and only if exactly one byte range has been requested.
-	 */
-	private boolean isRangeSatisfiable(String rangeHeader) {
-		assert rangeHeader != null;
-		if (!rangeHeader.startsWith(RANGE_BYTE_PREFIX)) {
-			return false;
-		}
-		final String byteRangeSet = StringUtils.removeStartIgnoreCase(rangeHeader, RANGE_BYTE_PREFIX);
-		final String[] byteRanges = StringUtils.split(byteRangeSet, RANGE_SET_SEP);
-		if (byteRanges.length != 1) {
-			return false;
-		}
-		return true;
-	}
-
-	/**
-	 * Processes the given range header field, if it is supported. Only headers containing a single byte range are supported.<br/>
-	 * <code>
-	 * bytes=100-200<br/>
-	 * bytes=-500<br/>
-	 * bytes=1000-
-	 * </code>
-	 * 
-	 * @return Tuple of left and right range.
-	 * @throws DavException HTTP statuscode 400 for malformed requests.
-	 * @throws IllegalArgumentException If the given rangeHeader is not satisfiable. Check with {@link #isRangeSatisfiable(String)} before.
-	 */
-	private Pair<String, String> getRequestRange(String rangeHeader) throws DavException {
-		assert rangeHeader != null;
-		if (!rangeHeader.startsWith(RANGE_BYTE_PREFIX)) {
-			throw new IllegalArgumentException("Unsatisfiable range. Should have generated 416 resonse.");
-		}
-		final String byteRangeSet = StringUtils.removeStartIgnoreCase(rangeHeader, RANGE_BYTE_PREFIX);
-		final String[] byteRanges = StringUtils.split(byteRangeSet, RANGE_SET_SEP);
-		if (byteRanges.length != 1) {
-			throw new IllegalArgumentException("Unsatisfiable range. Should have generated 416 resonse.");
-		}
-		final String byteRange = byteRanges[0];
-		final String[] bytePos = StringUtils.splitPreserveAllTokens(byteRange, RANGE_SEP);
-		if (bytePos.length != 2 || bytePos[0].isEmpty() && bytePos[1].isEmpty()) {
-			throw new DavException(DavServletResponse.SC_BAD_REQUEST, "malformed range header: " + rangeHeader);
-		}
-		return new ImmutablePair<>(bytePos[0], bytePos[1]);
-	}
-
-	/**
-	 * @return Absolute file path for a given cleartext file resourcePath.
-	 * @throws NonExistingParentException If one ancestor of the encrypted path is missing
-	 */
-	Path getEncryptedFilePath(String relativeCleartextPath, boolean createNonExisting) throws NonExistingParentException {
-		assert relativeCleartextPath.startsWith("/");
-		final String parentCleartextPath = StringUtils.prependIfMissing(FilenameUtils.getPathNoEndSeparator(relativeCleartextPath), "/");
-		final Path parent = getEncryptedDirectoryPath(parentCleartextPath, createNonExisting);
-		final String cleartextFilename = FilenameUtils.getName(relativeCleartextPath);
-		try {
-			final String encryptedFilename = filenameTranslator.getEncryptedFilename(cleartextFilename);
-			return parent.resolve(encryptedFilename);
-		} catch (IOException e) {
-			throw new UncheckedIOException(e);
-		}
-	}
-
-	/**
-	 * @return Absolute file path for a given cleartext file resourcePath.
-	 * @throws NonExistingParentException If one ancestor of the encrypted path is missing
-	 */
-	Path getEncryptedDirectoryFilePath(String relativeCleartextPath, boolean createNonExisting) throws NonExistingParentException {
-		assert relativeCleartextPath.startsWith("/");
-		final String parentCleartextPath = StringUtils.prependIfMissing(FilenameUtils.getPathNoEndSeparator(relativeCleartextPath), "/");
-		final Path parent = getEncryptedDirectoryPath(parentCleartextPath, createNonExisting);
-		final String cleartextFilename = FilenameUtils.getName(relativeCleartextPath);
-		try {
-			final String encryptedFilename = filenameTranslator.getEncryptedDirFileName(cleartextFilename);
-			return parent.resolve(encryptedFilename);
-		} catch (IOException e) {
-			throw new UncheckedIOException(e);
-		}
-	}
-	
-	/**
-	 * @param createNonExisting if <code>false</code>, a {@link NonExistingParentException} will be thrown for missing ancestors.
-	 * @return Absolute directory path for a given cleartext directory resourcePath.
-	 * @throws NonExistingParentException if one ancestor directory is missing.
-	 */
-	private Path getEncryptedDirectoryPath(String relativeCleartextPath, boolean createNonExisting) throws NonExistingParentException {
-		assert relativeCleartextPath.startsWith("/");
-		assert "/".equals(relativeCleartextPath) || !relativeCleartextPath.endsWith("/");
-		try {
-			final Path result;
-			if ("/".equals(relativeCleartextPath)) {
-				// root level
-				final String fixedRootDirectory = cryptor.encryptDirectoryPath("", FileSystems.getDefault().getSeparator());
-				result = dataRoot.resolve(fixedRootDirectory);
-			} else {
-				final String parentCleartextPath = StringUtils.prependIfMissing(FilenameUtils.getPathNoEndSeparator(relativeCleartextPath), "/");
-				final Path parent = getEncryptedDirectoryPath(parentCleartextPath, createNonExisting);
-				final String cleartextFilename = FilenameUtils.getName(relativeCleartextPath);
-				final String encryptedFilename = filenameTranslator.getEncryptedDirFileName(cleartextFilename);
-				final Path directoryFile = parent.resolve(encryptedFilename);
-				if (!createNonExisting && !Files.exists(directoryFile)) {
-					throw new NonExistingParentException();
-				}
-				final String directoryId = filenameTranslator.getDirectoryId(directoryFile, true);
-				final String directory = cryptor.encryptDirectoryPath(directoryId, FileSystems.getDefault().getSeparator());
-				result = dataRoot.resolve(directory);
-			}
-			Files.createDirectories(result);
-			return result;
-		} catch (IOException e) {
-			throw new UncheckedIOException(e);
-		}
-	}
-
-	private EncryptedFile createFilePart(DavResourceLocator locator, DavSession session, Pair<String, String> requestRange, Path filePath) {
-		return new EncryptedFilePart(this, locator, session, requestRange, lockManager, cryptor, cryptoWarningHandler, filePath);
-	}
-
-	private EncryptedFile createFile(DavResourceLocator locator, DavSession session, Path filePath) {
-		return new EncryptedFile(this, locator, session, lockManager, cryptor, cryptoWarningHandler, filePath);
-	}
-
-	private EncryptedDir createRootDirectory(DavResourceLocator locator, DavSession session) throws DavException {
-		final Path rootFile = dataRoot.resolve(ROOT_FILE);
-		final Path rootDir = filenameTranslator.getEncryptedDirectoryPath("");
-		try {
-			// make sure, root dir always exists.
-			// create dir first (because it fails silently, if alreay existing)
-			Files.createDirectories(rootDir);
-			Files.createFile(rootFile);
-		} catch (FileAlreadyExistsException e) {
-			// no-op
-		} catch (IOException e) {
-			throw new DavException(DavServletResponse.SC_INTERNAL_SERVER_ERROR);
-		}
-		return createDirectory(locator, session, dataRoot.resolve(ROOT_FILE));
-	}
-
-	private EncryptedDir createDirectory(DavResourceLocator locator, DavSession session, Path filePath) {
-		return new EncryptedDir(this, locator, session, lockManager, cryptor, filenameTranslator, filePath);
-	}
-
-	private NonExistingNode createNonExisting(DavResourceLocator locator, DavSession session) {
-		return new NonExistingNode(this, locator, session, lockManager, cryptor);
-	}
-	
-	static class NonExistingParentException extends Exception {
-		
-		private static final long serialVersionUID = 4421121746624627094L;
-		
-	}
-
-}

+ 0 - 26
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoWarningHandler.java

@@ -1,26 +0,0 @@
-package org.cryptomator.webdav.jackrabbit;
-
-import java.util.Collection;
-
-class CryptoWarningHandler {
-
-	private final Collection<String> resourcesWithInvalidMac;
-	private final Collection<String> whitelistedResources;
-
-	public CryptoWarningHandler(Collection<String> resourcesWithInvalidMac, Collection<String> whitelistedResources) {
-		this.resourcesWithInvalidMac = resourcesWithInvalidMac;
-		this.whitelistedResources = whitelistedResources;
-	}
-
-	public void macAuthFailed(String resourcePath) {
-		// collection might be a list, but we don't want duplicates:
-		if (!resourcesWithInvalidMac.contains(resourcePath)) {
-			resourcesWithInvalidMac.add(resourcePath);
-		}
-	}
-
-	public boolean ignoreMac(String resourcePath) {
-		return whitelistedResources.contains(resourcePath);
-	}
-
-}

+ 0 - 45
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionImpl.java

@@ -1,45 +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.webdav.jackrabbit;
-
-import java.util.HashSet;
-
-import org.apache.jackrabbit.webdav.DavSession;
-
-class DavSessionImpl implements DavSession {
-	
-	private final HashSet<String> lockTokens = new HashSet<String>();
-	private final HashSet<Object> references = new HashSet<Object>();
-
-	@Override
-	public void addReference(Object reference) {
-		references.add(reference);
-	}
-
-	@Override
-	public void removeReference(Object reference) {
-		references.remove(reference);
-	}
-
-	@Override
-	public void addLockToken(String token) {
-		lockTokens.add(token);
-	}
-
-	@Override
-	public String[] getLockTokens() {
-		return lockTokens.toArray(new String[lockTokens.size()]);
-	}
-
-	@Override
-	public void removeLockToken(String token) {
-		lockTokens.remove(token);
-	}
-
-}

+ 0 - 36
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionProviderImpl.java

@@ -1,36 +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.webdav.jackrabbit;
-
-import org.apache.jackrabbit.webdav.DavException;
-import org.apache.jackrabbit.webdav.DavSession;
-import org.apache.jackrabbit.webdav.DavSessionProvider;
-import org.apache.jackrabbit.webdav.WebdavRequest;
-
-class DavSessionProviderImpl implements DavSessionProvider {
-
-	@Override
-	public boolean attachSession(WebdavRequest request) throws DavException {
-		// every request gets a session
-		final DavSession session = new DavSessionImpl();
-		session.addReference(request);
-		request.setDavSession(session);
-		return true;
-	}
-
-	@Override
-	public void releaseSession(WebdavRequest request) {
-		final DavSession session = request.getDavSession();
-		if (session != null) {
-			session.removeReference(request);
-			request.setDavSession(null);
-		}
-	}
-
-}

+ 0 - 418
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java

@@ -1,418 +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.webdav.jackrabbit;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.AtomicMoveNotSupportedException;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.StandardOpenOption;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.nio.file.attribute.FileTime;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Queue;
-import java.util.UUID;
-import java.util.concurrent.LinkedTransferQueue;
-
-import org.apache.commons.io.FilenameUtils;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.jackrabbit.webdav.DavException;
-import org.apache.jackrabbit.webdav.DavResource;
-import org.apache.jackrabbit.webdav.DavResourceIterator;
-import org.apache.jackrabbit.webdav.DavResourceIteratorImpl;
-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.ActiveLock;
-import org.apache.jackrabbit.webdav.lock.LockManager;
-import org.apache.jackrabbit.webdav.property.DavPropertyName;
-import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
-import org.apache.jackrabbit.webdav.property.ResourceType;
-import org.cryptomator.crypto.Cryptor;
-import org.cryptomator.crypto.exceptions.DecryptFailedException;
-import org.cryptomator.crypto.exceptions.EncryptFailedException;
-import org.cryptomator.webdav.exceptions.DavRuntimeException;
-import org.eclipse.jetty.util.StringUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-class EncryptedDir extends AbstractEncryptedNode implements FileConstants {
-
-	private static final Logger LOG = LoggerFactory.getLogger(EncryptedDir.class);
-	private final FilenameTranslator filenameTranslator;
-	private String directoryId;
-	private Path directoryPath;
-
-	public EncryptedDir(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, FilenameTranslator filenameTranslator, Path filePath) {
-		super(factory, locator, session, lockManager, cryptor, filePath);
-		this.filenameTranslator = filenameTranslator;
-		properties.add(new ResourceType(ResourceType.COLLECTION));
-		properties.add(new DefaultDavProperty<Integer>(DavPropertyName.ISCOLLECTION, 1));
-	}
-
-	/**
-	 * @return Path or <code>null</code>, if directory does not yet exist.
-	 */
-	protected synchronized String getDirectoryId() {
-		if (directoryId == null) {
-			try {
-				directoryId = filenameTranslator.getDirectoryId(filePath, false);
-			} catch (IOException e) {
-				throw new UncheckedIOException(e);
-			}
-		}
-		return directoryId;
-	}
-
-	/**
-	 * @return Path or <code>null</code>, if directory does not yet exist.
-	 */
-	private synchronized Path getDirectoryPath() {
-		if (directoryPath == null) {
-			final String dirId = getDirectoryId();
-			if (dirId != null) {
-				directoryPath = filenameTranslator.getEncryptedDirectoryPath(directoryId);
-			}
-		}
-		return directoryPath;
-	}
-	
-	@Override
-	public boolean exists() {
-		return Files.exists(filePath) && Files.exists(getDirectoryPath());
-	}
-
-	@Override
-	public boolean isCollection() {
-		return true;
-	}
-
-	@Override
-	public long getModificationTime() {
-		try {
-			final Path dirPath = getDirectoryPath();
-			if (dirPath == null) {
-				return -1;
-			} else {
-				return Files.getLastModifiedTime(dirPath).toMillis();
-			}
-		} catch (IOException e) {
-			return -1;
-		}
-	}
-
-	@Override
-	public void addMember(DavResource resource, InputContext inputContext) throws DavException {
-		if (resource instanceof AbstractEncryptedNode) {
-			addMember((AbstractEncryptedNode) resource, inputContext);
-		} else {
-			throw new IllegalArgumentException("Unsupported resource type: " + resource.getClass().getName());
-		}
-	}
-
-	private void addMember(AbstractEncryptedNode childResource, InputContext inputContext) throws DavException {
-		if (childResource.isCollection()) {
-			this.addMemberDir(childResource.getLocator(), inputContext);
-		} else {
-			this.addMemberFile(childResource.getLocator(), inputContext);
-		}
-	}
-
-	private void addMemberDir(DavResourceLocator childLocator, InputContext inputContext) throws DavException {
-		final Path dirPath = getDirectoryPath();
-		if (dirPath == null) {
-			throw new DavException(DavServletResponse.SC_NOT_FOUND);
-		}
-		try {
-			final String cleartextDirName = FilenameUtils.getName(childLocator.getResourcePath());
-			final String ciphertextDirName = filenameTranslator.getEncryptedDirFileName(cleartextDirName);
-			final Path dirFilePath = dirPath.resolve(ciphertextDirName);
-			final String directoryId = filenameTranslator.getDirectoryId(dirFilePath, true);
-			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 dirPath = getDirectoryPath();
-		if (dirPath == null) {
-			throw new DavException(DavServletResponse.SC_NOT_FOUND);
-		}
-		try {
-			final String cleartextFilename = FilenameUtils.getName(childLocator.getResourcePath());
-			final String ciphertextFilename = filenameTranslator.getEncryptedFilename(cleartextFilename);
-			final Path filePath = dirPath.resolve(ciphertextFilename);
-			final Path tmpFilePath = Files.createTempFile(dirPath, null, null);
-			// encrypt to tmp file:
-			try (final FileChannel c = FileChannel.open(tmpFilePath, StandardOpenOption.WRITE, StandardOpenOption.DSYNC)) {
-				cryptor.encryptFile(inputContext.getInputStream(), c);
-			} catch (SecurityException e) {
-				throw new DavException(DavServletResponse.SC_FORBIDDEN, 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());
-			}
-			// mv tmp to target file:
-			try {
-				Files.move(tmpFilePath, filePath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
-			} catch (AtomicMoveNotSupportedException e) {
-				Files.move(tmpFilePath, filePath, StandardCopyOption.REPLACE_EXISTING);
-			}
-			Files.setLastModifiedTime(filePath, FileTime.from(Instant.now()));
-		} catch (IOException e) {
-			LOG.error("Failed to create file.", e);
-			throw new UncheckedIOException(e);
-		}
-	}
-
-	@Override
-	public DavResourceIterator getMembers() {
-		final Path dirPath = getDirectoryPath();
-		if (dirPath == null) {
-			throw new DavRuntimeException(new DavException(DavServletResponse.SC_NOT_FOUND));
-		}
-		
-		try (final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(dirPath, DIRECTORY_CONTENT_FILTER)) {	
-			final List<DavResource> result = new ArrayList<>();
-			for (final Path childPath : directoryStream) {
-				try {
-					final String cleartextFilename = filenameTranslator.getCleartextFilename(childPath.getFileName().toString());
-					final String cleartextFilepath = locator.isRootLocation() ? '/' + cleartextFilename : locator.getResourcePath() + '/' + cleartextFilename;
-					final DavResourceLocator childLocator = locator.getFactory().createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), cleartextFilepath);
-					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);
-					}
-					if (resource.exists()) {
-						result.add(resource);
-					}
-				} catch (DecryptFailedException e) {
-					LOG.warn("Decryption of resource failed: " + childPath);
-					continue;
-				}
-			}
-			return new DavResourceIteratorImpl(result);
-		} catch (IOException e) {
-			LOG.error("Exception during getMembers.", e);
-			throw new UncheckedIOException(e);
-		} catch (DavException e) {
-			LOG.error("Exception during getMembers.", e);
-			throw new DavRuntimeException(e);
-		}
-	}
-
-	@Override
-	public void removeMember(DavResource member) throws DavException {
-		if (member instanceof AbstractEncryptedNode) {
-			removeMember((AbstractEncryptedNode) member);
-		} else {
-			throw new IllegalArgumentException("Unsupported resource type: " + member.getClass().getName());
-		}
-	}
-
-	private void removeMember(AbstractEncryptedNode member) throws DavException {
-		final Path dirPath = getDirectoryPath();
-		if (dirPath == null) {
-			throw new DavException(DavServletResponse.SC_NOT_FOUND);
-		}
-		// https://tools.ietf.org/html/rfc4918#section-9.6
-		// we must unlock anything we want to delete:
-		for (ActiveLock lock : member.getLocks()) {
-			member.unlock(lock.getToken());
-		}
-		// now we can delete the file or directory:
-		try {
-			final String cleartextFilename = FilenameUtils.getName(member.getResourcePath());
-			if (member instanceof EncryptedDir) {
-				final EncryptedDir subDir = (EncryptedDir) member;
-				deleteSubDirectory(subDir);
-			} else {
-				final String ciphertextFilename = filenameTranslator.getEncryptedFilename(cleartextFilename);
-				final Path memberPath = dirPath.resolve(ciphertextFilename);
-				Files.deleteIfExists(memberPath);
-			}
-		} catch (FileNotFoundException e) {
-			// no-op
-		} catch (IOException e) {
-			throw new UncheckedIOException(e);
-		}
-	}
-
-	@Override
-	public void move(AbstractEncryptedNode dest) throws DavException, IOException {
-		// when moving a directory we only need to move the file (actual dir is ID-dependent and won't change)
-		final Path srcPath = filePath;
-		final Path dstPath;
-		if (dest instanceof NonExistingNode) {
-			dstPath = ((NonExistingNode) dest).materializeDirFilePath();
-		} else {
-			dstPath = dest.filePath;
-		}
-
-		// move:
-		Files.createDirectories(dstPath.getParent());
-		try {
-			Files.move(srcPath, dstPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
-		} catch (AtomicMoveNotSupportedException e) {
-			Files.move(srcPath, dstPath, StandardCopyOption.REPLACE_EXISTING);
-		}
-	}
-
-	@Override
-	public void copy(AbstractEncryptedNode dest, boolean shallow) throws DavException, IOException {
-		final Path dstDirFilePath;
-		if (dest instanceof NonExistingNode) {
-			dstDirFilePath = ((NonExistingNode) dest).materializeDirFilePath();
-		} else {
-			dstDirFilePath = dest.filePath;
-		}
-
-		// copy dirFile:
-		final String srcDirId = getDirectoryId();
-		if (srcDirId == null) {
-			throw new DavException(DavServletResponse.SC_NOT_FOUND);
-		}
-		final String dstDirId = UUID.randomUUID().toString();
-		try (final FileChannel c = FileChannel.open(dstDirFilePath, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC);
-				SilentlyFailingFileLock lock = new SilentlyFailingFileLock(c, false)) {
-			c.write(ByteBuffer.wrap(dstDirId.getBytes(StandardCharsets.UTF_8)));
-		}
-
-		// copy actual dir:
-		if (!shallow) {
-			copyDirectoryContents(srcDirId, dstDirId);
-		} else {
-			final Path dstDirPath = filenameTranslator.getEncryptedDirectoryPath(dstDirId);
-			Files.createDirectories(dstDirPath);
-		}
-	}
-
-	private void copyDirectoryContents(String srcDirId, String dstDirId) throws IOException {
-		final Path srcDirPath = filenameTranslator.getEncryptedDirectoryPath(srcDirId);
-		final Path dstDirPath = filenameTranslator.getEncryptedDirectoryPath(dstDirId);
-		Files.createDirectories(dstDirPath);
-		final DirectoryStream<Path> directoryStream = Files.newDirectoryStream(srcDirPath, DIRECTORY_CONTENT_FILTER);
-		for (final Path srcChildPath : directoryStream) {
-			final String childName = srcChildPath.getFileName().toString();
-			final Path dstChildPath = dstDirPath.resolve(childName);
-			if (StringUtils.endsWithIgnoreCase(childName, FILE_EXT)) {
-				try {
-					Files.copy(srcChildPath, dstChildPath, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
-				} catch (AtomicMoveNotSupportedException e) {
-					Files.copy(srcChildPath, dstChildPath, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
-				}
-			} else if (StringUtils.endsWithIgnoreCase(childName, DIR_EXT)) {
-				final String srcSubdirId = filenameTranslator.getDirectoryId(srcChildPath, false);
-				final String dstSubdirId = filenameTranslator.getDirectoryId(dstChildPath, true);
-				copyDirectoryContents(srcSubdirId, dstSubdirId);
-			}
-		}
-	}
-
-	@Override
-	public void spool(OutputContext outputContext) throws IOException {
-		// do nothing
-	}
-	
-	/**
-	 * Deletes a given directory recursively by resolving subdirectories using their directory files.
-	 */
-	private void deleteSubDirectory(final EncryptedDir subDir) throws IOException {
-		final Path subDirPath = subDir.getDirectoryPath();
-		filenameTranslator.uncacheDirectoryId(subDir.filePath);
-		Files.delete(subDir.filePath);
-		final LinkedTransferQueue<Path> queue = new LinkedTransferQueue<>();
-		queue.put(subDirPath);
-		Path dir;
-		while ((dir = queue.poll()) != null) {
-			if (Files.exists(dir)) {
-				Files.walkFileTree(dir, new RecursiveDirectoryDeletingVisitor(queue));
-			}
-		}
-	}
-	
-	/**
-	 * Deletes all files it visits and enqueues subdirectories into a given {@link Queue} for deletion, too.
-	 * 
-	 * If its parent directory is empty after deleting, it will get deleted, too.
-	 */
-	private class RecursiveDirectoryDeletingVisitor extends SimpleFileVisitor<Path> {
-		
-		private final Queue<Path> directories;
-		
-		private RecursiveDirectoryDeletingVisitor(Queue<Path> directories) {
-			this.directories = directories;
-		}
-
-		@Override
-		public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) throws IOException {
-			if (file.toString().endsWith(DIR_EXT)) {
-				final String directoryId = filenameTranslator.getDirectoryId(file, false);
-				final Path directoryPath = filenameTranslator.getEncryptedDirectoryPath(directoryId);
-				directories.add(directoryPath);
-				filenameTranslator.uncacheDirectoryId(file);
-			}
-			Files.delete(file);
-			return FileVisitResult.CONTINUE;
-		}
-
-		@Override
-		public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
-			// first check, if we're the only remaining child:
-			boolean hasSiblings = false;
-			try (final DirectoryStream<Path> siblings = Files.newDirectoryStream(dir.getParent())) {
-				for (Path sibling : siblings) {
-					if (!dir.getFileName().equals(sibling.getFileName())) {
-						hasSiblings = true;
-						break;
-					}
-				}
-			}
-			// delete our current directory:
-			Files.delete(dir);
-			// if we have siblings, we still need our parent. Otherwise delete it, too:
-			if (!hasSiblings) {
-				Files.delete(dir.getParent());
-			}
-			return FileVisitResult.CONTINUE;
-		}
-
-		@Override
-		public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
-			LOG.error("Failed to delete file " + file.toString(), exc);
-			return FileVisitResult.TERMINATE;
-		}
-
-	}
-
-
-}

+ 0 - 154
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java

@@ -1,154 +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.webdav.jackrabbit;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.channels.FileChannel;
-import java.nio.channels.OverlappingFileLockException;
-import java.nio.file.AtomicMoveNotSupportedException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.StandardOpenOption;
-
-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.DavSession;
-import org.apache.jackrabbit.webdav.io.InputContext;
-import org.apache.jackrabbit.webdav.io.OutputContext;
-import org.apache.jackrabbit.webdav.lock.LockManager;
-import org.apache.jackrabbit.webdav.property.DavPropertyName;
-import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
-import org.cryptomator.crypto.Cryptor;
-import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
-import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.http.HttpHeaderValue;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-class EncryptedFile extends AbstractEncryptedNode implements FileConstants {
-
-	private static final Logger LOG = LoggerFactory.getLogger(EncryptedFile.class);
-
-	protected final CryptoWarningHandler cryptoWarningHandler;
-	protected final Long contentLength;
-
-	public EncryptedFile(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor, CryptoWarningHandler cryptoWarningHandler, Path filePath) {
-		super(factory, locator, session, lockManager, cryptor, filePath);
-		if (filePath == null) {
-			throw new IllegalArgumentException("filePath must not be null");
-		}
-		this.cryptoWarningHandler = cryptoWarningHandler;
-		Long contentLength = null;
-		if (Files.isRegularFile(filePath)) {
-			try (final FileChannel c = FileChannel.open(filePath, StandardOpenOption.READ, StandardOpenOption.DSYNC); SilentlyFailingFileLock lock = new SilentlyFailingFileLock(c, true)) {
-				contentLength = cryptor.decryptedContentLength(c);
-				properties.add(new DefaultDavProperty<Long>(DavPropertyName.GETCONTENTLENGTH, contentLength));
-				if (contentLength > RANGE_REQUEST_LOWER_LIMIT) {
-					properties.add(new HttpHeaderProperty(HttpHeader.ACCEPT_RANGES.asString(), HttpHeaderValue.BYTES.asString()));
-				}
-			} catch (OverlappingFileLockException e) {
-				// file header currently locked, report -1 for unknown size.
-				properties.add(new DefaultDavProperty<Long>(DavPropertyName.GETCONTENTLENGTH, -1l));
-			} catch (MacAuthenticationFailedException e) {
-				LOG.warn("Content length couldn't be determined due to MAC authentication violation.");
-				// don't add content length DAV property
-			} catch (IOException e) {
-				LOG.error("Error reading filesize " + filePath.toString(), e);
-				throw new UncheckedIOException(e);
-			}
-		}
-		this.contentLength = contentLength;
-	}
-
-	public Long getContentLength() {
-		return contentLength;
-	}
-
-	@Override
-	public boolean isCollection() {
-		return false;
-	}
-
-	@Override
-	public void addMember(DavResource resource, InputContext inputContext) throws DavException {
-		throw new UnsupportedOperationException("Can not add member to file.");
-	}
-
-	@Override
-	public DavResourceIterator getMembers() {
-		throw new UnsupportedOperationException("Can not list members of file.");
-	}
-
-	@Override
-	public void removeMember(DavResource member) throws DavException {
-		throw new UnsupportedOperationException("Can not remove member to file.");
-	}
-
-	@Override
-	public void spool(OutputContext outputContext) throws IOException {
-		if (Files.isRegularFile(filePath)) {
-			outputContext.setModificationTime(Files.getLastModifiedTime(filePath).toMillis());
-			outputContext.setProperty(HttpHeader.ACCEPT_RANGES.asString(), HttpHeaderValue.BYTES.asString());
-			try (final FileChannel c = FileChannel.open(filePath, StandardOpenOption.READ); SilentlyFailingFileLock lock = new SilentlyFailingFileLock(c, true)) {
-				final Long contentLength = cryptor.decryptedContentLength(c);
-				if (contentLength != null) {
-					outputContext.setContentLength(contentLength);
-				}
-				if (outputContext.hasStream()) {
-					final boolean authenticate = !cryptoWarningHandler.ignoreMac(getLocator().getResourcePath());
-					cryptor.decryptFile(c, outputContext.getOutputStream(), authenticate);
-					outputContext.getOutputStream().flush();
-				}
-
-			} catch (EOFException e) {
-				LOG.warn("Unexpected end of stream (possibly client hung up).");
-			}
-		}
-	}
-
-	@Override
-	public void move(AbstractEncryptedNode dest) throws DavException, IOException {
-		final Path srcPath = filePath;
-		final Path dstPath;
-		if (dest instanceof NonExistingNode) {
-			dstPath = ((NonExistingNode) dest).materializeFilePath();
-		} else {
-			dstPath = dest.filePath;
-		}
-
-		try {
-			Files.move(srcPath, dstPath, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
-		} catch (AtomicMoveNotSupportedException e) {
-			Files.move(srcPath, dstPath, StandardCopyOption.REPLACE_EXISTING);
-		}
-	}
-
-	@Override
-	public void copy(AbstractEncryptedNode dest, boolean shallow) throws DavException, IOException {
-		final Path srcPath = filePath;
-		final Path dstPath;
-		if (dest instanceof NonExistingNode) {
-			dstPath = ((NonExistingNode) dest).materializeFilePath();
-		} else {
-			dstPath = dest.filePath;
-		}
-
-		try {
-			Files.copy(srcPath, dstPath, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
-		} catch (AtomicMoveNotSupportedException e) {
-			Files.copy(srcPath, dstPath, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING);
-		}
-	}
-
-}

+ 0 - 90
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFilePart.java

@@ -1,90 +0,0 @@
-package org.cryptomator.webdav.jackrabbit;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.nio.channels.FileChannel;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-
-import org.apache.commons.lang3.tuple.ImmutablePair;
-import org.apache.commons.lang3.tuple.Pair;
-import org.apache.jackrabbit.webdav.DavResourceLocator;
-import org.apache.jackrabbit.webdav.DavSession;
-import org.apache.jackrabbit.webdav.io.OutputContext;
-import org.apache.jackrabbit.webdav.lock.LockManager;
-import org.cryptomator.crypto.Cryptor;
-import org.eclipse.jetty.http.HttpHeader;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Delivers only the requested range of bytes from a file.
- * 
- * @see {@link https://tools.ietf.org/html/rfc7233#section-4}
- */
-class EncryptedFilePart extends EncryptedFile {
-
-	private static final Logger LOG = LoggerFactory.getLogger(EncryptedFilePart.class);
-
-	private final Pair<Long, Long> range;
-
-	public EncryptedFilePart(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, Pair<String, String> requestRange, LockManager lockManager, Cryptor cryptor,
-			CryptoWarningHandler cryptoWarningHandler, Path filePath) {
-		super(factory, locator, session, lockManager, cryptor, cryptoWarningHandler, filePath);
-
-		try {
-			final Long lower = requestRange.getLeft().isEmpty() ? null : Long.valueOf(requestRange.getLeft());
-			final Long upper = requestRange.getRight().isEmpty() ? null : Long.valueOf(requestRange.getRight());
-			if (lower == null) {
-				range = new ImmutablePair<Long, Long>(contentLength - upper, contentLength - 1);
-			} else if (upper == null) {
-				range = new ImmutablePair<Long, Long>(lower, contentLength - 1);
-			} else {
-				range = new ImmutablePair<Long, Long>(lower, Math.min(upper, contentLength - 1));
-			}
-		} catch (NumberFormatException e) {
-			throw new IllegalArgumentException("Invalid byte range: " + requestRange, e);
-		}
-	}
-
-	@Override
-	public void spool(OutputContext outputContext) throws IOException {
-		assert Files.isRegularFile(filePath);
-		assert contentLength != null;
-
-		final Long rangeLength = range.getRight() - range.getLeft() + 1;
-		outputContext.setModificationTime(Files.getLastModifiedTime(filePath).toMillis());
-		if (rangeLength <= 0 || range.getLeft() > contentLength - 1) {
-			// unsatisfiable content range:
-			outputContext.setContentLength(0);
-			outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), "bytes */" + contentLength);
-			LOG.debug("Requested content range unsatisfiable: " + getContentRangeHeader(range.getLeft(), range.getRight(), contentLength));
-			return;
-		} else {
-			outputContext.setContentLength(rangeLength);
-			outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), getContentRangeHeader(range.getLeft(), range.getRight(), contentLength));
-		}
-
-		assert range.getLeft() > 0;
-		assert range.getLeft() < contentLength;
-		assert range.getRight() < contentLength;
-
-		try (final FileChannel c = FileChannel.open(filePath, StandardOpenOption.READ)) {
-			if (outputContext.hasStream()) {
-				final boolean authenticate = !cryptoWarningHandler.ignoreMac(getLocator().getResourcePath());
-				cryptor.decryptRange(c, outputContext.getOutputStream(), range.getLeft(), rangeLength, authenticate);
-				outputContext.getOutputStream().flush();
-			}
-		} catch (EOFException e) {
-			if (LOG.isDebugEnabled()) {
-				LOG.trace("Unexpected end of stream during delivery of partial content (client hung up).");
-			}
-		}
-	}
-
-	private String getContentRangeHeader(long firstByte, long lastByte, long completeLength) {
-		return String.format("bytes %d-%d/%d", firstByte, lastByte, completeLength);
-	}
-
-}

+ 0 - 108
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileConstants.java

@@ -1,108 +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.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.lang3.StringUtils;
-
-interface FileConstants {
-
-	/**
-	 * Number of bytes in the file header.
-	 */
-	long FILE_HEADER_LENGTH = 104;
-
-	/**
-	 * Allow range requests for files > 32MiB.
-	 */
-	long RANGE_REQUEST_LOWER_LIMIT = 32 * 1024 * 1024;
-
-	/**
-	 * Maximum path length on some file systems or cloud storage providers is restricted.<br/>
-	 * Parent folder path uses up to 58 chars (sha256 -&gt; 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.<br/>
-	 * 128 chars would be enought for up to 80 plaintext chars. Also we need up to 9 chars for our file extension. So lets use {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT}.
-	 */
-	int ENCRYPTED_FILENAME_LENGTH_LIMIT = 137;
-
-	/**
-	 * Dummy file, on which file attributes can be stored for the root directory.
-	 */
-	String ROOT_FILE = "root";
-
-	/**
-	 * For encrypted directory names <= {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT} chars.
-	 */
-	String DIR_EXT = ".dir";
-
-	/**
-	 * For encrypted direcotry names > {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT} chars.
-	 */
-	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.
-	 */
-	int LONG_NAME_PREFIX_LENGTH = 8;
-
-	/**
-	 * Matches valid encrypted filenames (both normal and long filenames - see {@link #ENCRYPTED_FILENAME_LENGTH_LIMIT}).
-	 */
-	PathMatcher ENCRYPTED_FILE_MATCHER = new PathMatcher() {
-
-		private final Pattern BASIC_NAME_PATTERN = Pattern.compile("^[a-z2-7]+=*$", Pattern.CASE_INSENSITIVE);
-		private final Pattern LONG_NAME_PATTERN = Pattern.compile("^[a-z2-7]{8}[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", Pattern.CASE_INSENSITIVE);
-
-		@Override
-		public boolean matches(Path path) {
-			final String filename = path.getFileName().toString();
-			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, 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;
-			}
-		}
-
-	};
-
-	/**
-	 * Filter to determine files of interest in encrypted directory. Based on {@link #ENCRYPTED_FILE_MATCHER}.
-	 */
-	Filter<Path> DIRECTORY_CONTENT_FILTER = new Filter<Path>() {
-		@Override
-		public boolean accept(Path entry) throws IOException {
-			return ENCRYPTED_FILE_MATCHER.matches(entry);
-		}
-	};
-
-}

+ 0 - 34
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileTimeUtils.java

@@ -1,34 +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.webdav.jackrabbit;
-
-import java.nio.file.attribute.FileTime;
-import java.time.Instant;
-import java.time.OffsetDateTime;
-import java.time.ZoneOffset;
-import java.time.format.DateTimeFormatter;
-import java.time.temporal.Temporal;
-
-final class FileTimeUtils {
-
-	private FileTimeUtils() {
-		throw new IllegalStateException("not instantiable");
-	}
-
-	static String toRfc1123String(FileTime time) {
-		final Temporal date = OffsetDateTime.ofInstant(time.toInstant(), ZoneOffset.UTC);
-		return DateTimeFormatter.RFC_1123_DATE_TIME.format(date);
-	}
-
-	static FileTime fromRfc1123String(String string) {
-		final Instant instant = Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(string));
-		return FileTime.from(instant);
-	}
-
-}

+ 0 - 234
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java

@@ -1,234 +0,0 @@
-package org.cryptomator.webdav.jackrabbit;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.Serializable;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
-import java.nio.file.attribute.FileTime;
-import java.util.Map;
-import java.util.UUID;
-
-import org.apache.commons.collections4.BidiMap;
-import org.apache.commons.collections4.bidimap.DualHashBidiMap;
-import org.apache.commons.collections4.map.LRUMap;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.ImmutablePair;
-import org.apache.commons.lang3.tuple.Pair;
-import org.cryptomator.crypto.Cryptor;
-import org.cryptomator.crypto.exceptions.DecryptFailedException;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-
-class FilenameTranslator implements FileConstants {
-
-	private static final int MAX_CACHED_DIRECTORY_IDS = 5000;
-	private static final int MAX_CACHED_METADATA_FILES = 1000;
-
-	private final Cryptor cryptor;
-	private final Path dataRoot;
-	private final Path metadataRoot;
-	private final ObjectMapper objectMapper = new ObjectMapper();
-	private final Map<Pair<Path, FileTime>, String> directoryIdCache = new LRUMap<>(MAX_CACHED_DIRECTORY_IDS); // <directoryFile, directoryId>
-	private final Map<Pair<Path, FileTime>, LongFilenameMetadata> metadataCache = new LRUMap<>(MAX_CACHED_METADATA_FILES); // <metadataFile, metadata>
-
-	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 String getDirectoryId(Path directoryFile, boolean createIfNonexisting) throws IOException {
-		try {
-			final Pair<Path, FileTime> key = ImmutablePair.of(directoryFile, Files.getLastModifiedTime(directoryFile));
-			String directoryId = directoryIdCache.get(key);
-			if (directoryId == null) {
-				directoryId = new String(readAllBytesAtomically(directoryFile), StandardCharsets.UTF_8);
-				directoryIdCache.put(key, directoryId);
-			}
-			return directoryId;
-		} catch (FileNotFoundException | NoSuchFileException e) {
-			if (createIfNonexisting) {
-				final String directoryId = UUID.randomUUID().toString();
-				writeAllBytesAtomically(directoryFile, directoryId.getBytes(StandardCharsets.UTF_8));
-				final Pair<Path, FileTime> key = ImmutablePair.of(directoryFile, Files.getLastModifiedTime(directoryFile));
-				directoryIdCache.put(key, directoryId);
-				return directoryId;
-			} else {
-				return null;
-			}
-		}
-	}
-	
-	/**
-	 * to be called when a directory gets deleted, so the corresponding directory id is not longer cached.
-	 */
-	public void uncacheDirectoryId(Path directoryFile) throws IOException {
-		final Pair<Path, FileTime> key = ImmutablePair.of(directoryFile, Files.getLastModifiedTime(directoryFile));
-		directoryIdCache.remove(key);
-	}
-
-	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 getEncryptedDirFileName(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.<br/>
-	 * This means that we need a workaround for filenames longer than the limit defined in {@link FileConstants#ENCRYPTED_FILENAME_LENGTH_LIMIT}.<br/>
-	 * <br/>
-	 * 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);
-	}
-
-	/* Locked I/O */
-
-	private void writeAllBytesAtomically(Path path, byte[] bytes) throws IOException {
-		try (final FileChannel c = FileChannel.open(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.DSYNC);
-				final SilentlyFailingFileLock lock = new SilentlyFailingFileLock(c, false)) {
-			c.write(ByteBuffer.wrap(bytes));
-		}
-	}
-
-	private byte[] readAllBytesAtomically(Path path) throws IOException {
-		try (final FileChannel c = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.DSYNC); final SilentlyFailingFileLock lock = new SilentlyFailingFileLock(c, true)) {
-			final ByteBuffer buffer = ByteBuffer.allocate((int) c.size());
-			c.read(buffer);
-			return buffer.array();
-		}
-	}
-
-	/* 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));
-
-		// evict previously cached entries:
-		try {
-			final Pair<Path, FileTime> key = ImmutablePair.of(metadataFile, Files.getLastModifiedTime(metadataFile));
-			metadataCache.remove(key);
-		} catch (FileNotFoundException | NoSuchFileException e) {
-			// didn't exist yet? then we don't need to do anything anyway.
-		}
-
-		// write:
-		final byte[] metadataContent = objectMapper.writeValueAsBytes(metadata);
-		writeAllBytesAtomically(metadataFile, metadataContent);
-
-		// add to cache:
-		final Pair<Path, FileTime> key = ImmutablePair.of(metadataFile, Files.getLastModifiedTime(metadataFile));
-		metadataCache.put(key, metadata);
-	}
-
-	private LongFilenameMetadata readMetadata(String metadataGroup) throws IOException {
-		final Path metadataDir = metadataRoot.resolve(metadataGroup.substring(0, 2));
-		final Path metadataFile = metadataDir.resolve(metadataGroup.substring(2));
-		try {
-			// use cached metadata, if possible:
-			final Pair<Path, FileTime> key = ImmutablePair.of(metadataFile, Files.getLastModifiedTime(metadataFile));
-			LongFilenameMetadata metadata = metadataCache.get(key);
-			// else read from filesystem:
-			if (metadata == null) {
-				final byte[] metadataContent = readAllBytesAtomically(metadataFile);
-				metadata = objectMapper.readValue(metadataContent, LongFilenameMetadata.class);
-				metadataCache.put(key, metadata);
-			}
-			return metadata;
-		} catch (FileNotFoundException | NoSuchFileException e) {
-			// not yet existing:
-			return new LongFilenameMetadata();
-		}
-	}
-
-	private static class LongFilenameMetadata implements Serializable {
-
-		private static final long serialVersionUID = 6214509403824421320L;
-
-		@JsonDeserialize(as = DualHashBidiMap.class)
-		private BidiMap<UUID, String> encryptedFilenames = new DualHashBidiMap<>();
-
-		/* Getter/Setter */
-
-		public synchronized String getEncryptedFilenameForUUID(final UUID uuid) {
-			return encryptedFilenames.get(uuid);
-		}
-
-		public synchronized UUID getOrCreateUuidForEncryptedFilename(String encryptedFilename) {
-			UUID uuid = encryptedFilenames.getKey(encryptedFilename);
-			if (uuid == null) {
-				uuid = UUID.randomUUID();
-				encryptedFilenames.put(uuid, encryptedFilename);
-			}
-			return uuid;
-		}
-
-		// used by jackson
-		@SuppressWarnings("unused")
-		public BidiMap<UUID, String> getEncryptedFilenames() {
-			return encryptedFilenames;
-		}
-
-		// used by jackson
-		@SuppressWarnings("unused")
-		public void setEncryptedFilenames(BidiMap<UUID, String> encryptedFilenames) {
-			this.encryptedFilenames = encryptedFilenames;
-		}
-
-	}
-
-}

+ 0 - 20
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/HttpHeaderProperty.java

@@ -1,20 +0,0 @@
-package org.cryptomator.webdav.jackrabbit;
-
-import org.apache.jackrabbit.webdav.property.AbstractDavProperty;
-import org.apache.jackrabbit.webdav.property.DavPropertyName;
-
-class HttpHeaderProperty extends AbstractDavProperty<String> {
-
-	private final String value;
-
-	public HttpHeaderProperty(String key, String value) {
-		super(DavPropertyName.create(key), true);
-		this.value = value;
-	}
-
-	@Override
-	public String getValue() {
-		return value;
-	}
-
-}

+ 0 - 104
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java

@@ -1,104 +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.webdav.jackrabbit;
-
-import java.io.IOException;
-import java.nio.file.Path;
-
-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.DavSession;
-import org.apache.jackrabbit.webdav.io.InputContext;
-import org.apache.jackrabbit.webdav.io.OutputContext;
-import org.apache.jackrabbit.webdav.lock.LockManager;
-import org.apache.jackrabbit.webdav.property.DavProperty;
-import org.cryptomator.crypto.Cryptor;
-import org.cryptomator.webdav.jackrabbit.CryptoResourceFactory.NonExistingParentException;
-
-class NonExistingNode extends AbstractEncryptedNode {
-
-	public NonExistingNode(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, LockManager lockManager, Cryptor cryptor) {
-		super(factory, locator, session, lockManager, cryptor, null);
-	}
-
-	@Override
-	public boolean exists() {
-		return false;
-	}
-
-	@Override
-	public boolean isCollection() {
-		return false;
-	}
-
-	@Override
-	public long getModificationTime() {
-		return -1;
-	}
-
-	@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.");
-	}
-
-	@Override
-	public void setProperty(DavProperty<?> property) throws DavException {
-		throw new UnsupportedOperationException("Resource doesn't exist.");
-	}
-
-	/**
-	 * @return lazily resolved file path, e.g. needed during MOVE operations.
-	 */
-	public Path materializeFilePath() {
-		try {
-			return factory.getEncryptedFilePath(locator.getResourcePath(), true);
-		} catch (NonExistingParentException e) {
-			throw new IllegalStateException(e);
-		}
-	}
-
-	/**
-	 * @return lazily resolved directory file path, e.g. needed during MOVE operations.
-	 */
-	public Path materializeDirFilePath() {
-		try {
-			return factory.getEncryptedDirectoryFilePath(locator.getResourcePath(), true);
-		} catch (NonExistingParentException e) {
-			throw new IllegalStateException(e);
-		}
-	}
-
-}

+ 0 - 56
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/SilentlyFailingFileLock.java

@@ -1,56 +0,0 @@
-package org.cryptomator.webdav.jackrabbit;
-
-import java.io.IOException;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.channels.NonReadableChannelException;
-import java.nio.channels.NonWritableChannelException;
-import java.nio.channels.OverlappingFileLockException;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Instances of this class wrap a file lock, that is created upon construction and destroyed by {@link #close()}.
- * 
- * If the construction fails (e.g. if the file system does not support locks) no exception will be thrown and no lock is created.
- */
-class SilentlyFailingFileLock implements AutoCloseable {
-
-	private static final Logger LOG = LoggerFactory.getLogger(SilentlyFailingFileLock.class);
-
-	private final FileLock lock;
-
-	/**
-	 * Invokes #SilentlyFailingFileLock(FileChannel, long, long, boolean) with a position of 0 and a size of {@link Long#MAX_VALUE}.
-	 */
-	SilentlyFailingFileLock(FileChannel channel, boolean shared) {
-		this(channel, 0L, Long.MAX_VALUE, shared);
-	}
-
-	/**
-	 * @throws NonReadableChannelException If shared is true this channel was not opened for reading
-	 * @throws NonWritableChannelException If shared is false but this channel was not opened for writing
-	 * @see FileChannel#lock(long, long, boolean)
-	 */
-	SilentlyFailingFileLock(FileChannel channel, long position, long size, boolean shared) {
-		FileLock lock = null;
-		try {
-			lock = channel.tryLock(position, size, shared);
-		} catch (IOException | OverlappingFileLockException e) {
-			if (LOG.isDebugEnabled()) {
-				LOG.trace("Unable to lock file.");
-			}
-		} finally {
-			this.lock = lock;
-		}
-	}
-
-	@Override
-	public void close() throws IOException {
-		if (lock != null) {
-			lock.close();
-		}
-	}
-
-}

+ 0 - 118
main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java

@@ -1,118 +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.webdav.jackrabbit;
-
-import java.io.IOException;
-import java.util.Collection;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletResponse;
-
-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.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;
-import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class WebDavServlet extends AbstractWebdavServlet {
-
-	private static final long serialVersionUID = 7965170007048673022L;
-	private static final Logger LOG = LoggerFactory.getLogger(WebDavServlet.class);
-	public static final String CFG_FS_ROOT = "cfg.fs.root";
-	private DavSessionProvider davSessionProvider;
-	private DavLocatorFactory davLocatorFactory;
-	private DavResourceFactory davResourceFactory;
-	private final Cryptor cryptor;
-	private final CryptoWarningHandler cryptoWarningHandler;
-
-	public WebDavServlet(final Cryptor cryptor, final Collection<String> failingMacCollection, final Collection<String> whitelistedResourceCollection) {
-		super();
-		this.cryptor = cryptor;
-		this.cryptoWarningHandler = new CryptoWarningHandler(failingMacCollection, whitelistedResourceCollection);
-	}
-
-	@Override
-	public void init(ServletConfig config) throws ServletException {
-		super.init(config);
-		final String fsRoot = config.getInitParameter(CFG_FS_ROOT);
-		davSessionProvider = new DavSessionProviderImpl();
-		davLocatorFactory = new CleartextLocatorFactory(config.getServletContext().getContextPath());
-		davResourceFactory = new CryptoResourceFactory(cryptor, cryptoWarningHandler, fsRoot);
-	}
-
-	@Override
-	protected boolean isPreconditionValid(WebdavRequest request, DavResource resource) {
-		return !resource.exists() || request.matchesIfHeader(resource);
-	}
-
-	@Override
-	public DavSessionProvider getDavSessionProvider() {
-		return davSessionProvider;
-	}
-
-	@Override
-	public void setDavSessionProvider(DavSessionProvider davSessionProvider) {
-		this.davSessionProvider = davSessionProvider;
-	}
-
-	@Override
-	public DavLocatorFactory getLocatorFactory() {
-		return davLocatorFactory;
-	}
-
-	@Override
-	public void setLocatorFactory(DavLocatorFactory locatorFactory) {
-		this.davLocatorFactory = locatorFactory;
-	}
-
-	@Override
-	public DavResourceFactory getResourceFactory() {
-		return davResourceFactory;
-	}
-
-	@Override
-	public void setResourceFactory(DavResourceFactory resourceFactory) {
-		this.davResourceFactory = resourceFactory;
-	}
-
-	@Override
-	protected void doPut(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException {
-		long t0 = System.nanoTime();
-		super.doPut(request, response, resource);
-		if (LOG.isDebugEnabled()) {
-			long t1 = System.nanoTime();
-			LOG.trace("PUT TIME: " + (t1 - t0) / 1000 / 1000.0 + " ms");
-		}
-	}
-
-	@Override
-	protected void doGet(WebdavRequest request, WebdavResponse response, DavResource resource) throws IOException, DavException {
-		long t0 = System.nanoTime();
-		try {
-			super.doGet(request, response, resource);
-		} catch (MacAuthenticationFailedException e) {
-			LOG.warn("File integrity violation for " + resource.getLocator().getResourcePath());
-			cryptoWarningHandler.macAuthFailed(resource.getLocator().getResourcePath());
-			response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
-		}
-		if (LOG.isDebugEnabled()) {
-			long t1 = System.nanoTime();
-			LOG.trace("GET TIME: " + (t1 - t0) / 1000 / 1000.0 + " ms");
-		}
-	}
-
-}

+ 0 - 103
main/core/src/test/java/org/cryptomator/webdav/jackrabbit/CryptorMock.java

@@ -1,103 +0,0 @@
-package org.cryptomator.webdav.jackrabbit;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.SeekableByteChannel;
-
-import org.cryptomator.crypto.Cryptor;
-import org.cryptomator.crypto.exceptions.DecryptFailedException;
-import org.cryptomator.crypto.exceptions.EncryptFailedException;
-import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
-import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
-import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
-import org.cryptomator.crypto.exceptions.WrongPasswordException;
-
-class CryptorMock implements Cryptor {
-
-	private static final int BUFSIZE = 32768;
-
-	@Override
-	public void randomizeMasterKey() {
-		// noop
-	}
-
-	@Override
-	public void encryptMasterKey(OutputStream out, CharSequence password) throws IOException {
-		// noop
-	}
-
-	@Override
-	public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException, UnsupportedVaultException {
-		// noop
-	}
-
-	@Override
-	public String encryptDirectoryPath(String cleartextDirectoryId, String nativePathSep) {
-		return cleartextDirectoryId;
-	}
-
-	@Override
-	public String encryptFilename(String cleartextName) {
-		return cleartextName;
-	}
-
-	@Override
-	public String decryptFilename(String ciphertextName) throws DecryptFailedException {
-		return ciphertextName;
-	}
-
-	@Override
-	public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException, MacAuthenticationFailedException {
-		return encryptedFile.size();
-	}
-
-	@Override
-	public Long decryptFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile, boolean authenticate) throws IOException, DecryptFailedException {
-		ByteBuffer buf = ByteBuffer.allocate(BUFSIZE);
-		long numReadTotal = 0;
-		int numRead = 0;
-		while ((numRead = encryptedFile.read(buf)) != -1) {
-			numReadTotal += numRead;
-			buf.flip();
-			byte[] bytes = new byte[numRead];
-			buf.get(bytes);
-			plaintextFile.write(bytes);
-			buf.rewind();
-		}
-		return numReadTotal;
-	}
-
-	@Override
-	public Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length, boolean authenticate) throws IOException, DecryptFailedException {
-		encryptedFile.position(pos);
-
-		ByteBuffer buf = ByteBuffer.allocate(BUFSIZE);
-		long numReadTotal = 0;
-		int numRead = 0;
-		while ((numRead = encryptedFile.read(buf)) != -1 && numReadTotal < length) {
-			int len = (int) Math.min(Math.min(numRead, BUFSIZE), length - numReadTotal); // known to fit into integer
-			numReadTotal += len;
-			buf.flip();
-			byte[] bytes = new byte[len];
-			buf.get(bytes);
-			plaintextFile.write(bytes);
-			buf.rewind();
-		}
-		return numReadTotal;
-	}
-
-	@Override
-	public Long encryptFile(InputStream plaintextFile, SeekableByteChannel encryptedFile) throws IOException, EncryptFailedException {
-		byte[] buf = new byte[BUFSIZE];
-		long numWrittenTotal = 0;
-		int numRead = 0;
-		while ((numRead = plaintextFile.read(buf)) != -1) {
-			numWrittenTotal += numRead;
-			encryptedFile.write(ByteBuffer.wrap(buf, 0, numRead));
-		}
-		return numWrittenTotal;
-	}
-
-}

+ 0 - 272
main/core/src/test/java/org/cryptomator/webdav/jackrabbit/RangeRequestTest.java

@@ -1,272 +0,0 @@
-package org.cryptomator.webdav.jackrabbit;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Random;
-import java.util.concurrent.ForkJoinPool;
-import java.util.concurrent.ForkJoinTask;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.HttpMethod;
-import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
-import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
-import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
-import org.apache.commons.httpclient.methods.GetMethod;
-import org.apache.commons.httpclient.methods.PutMethod;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
-import org.cryptomator.crypto.Cryptor;
-import org.cryptomator.webdav.WebDavServer;
-import org.cryptomator.webdav.WebDavServer.ServletLifeCycleAdapter;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.io.Files;
-
-public class RangeRequestTest {
-
-	private static final Logger LOG = LoggerFactory.getLogger(RangeRequestTest.class);
-	private static final Cryptor CRYPTOR = new CryptorMock();
-	private static final WebDavServer SERVER = new WebDavServer();
-	private static final File TMP_VAULT = Files.createTempDir();
-	private static ServletLifeCycleAdapter SERVLET;
-	private static URI VAULT_BASE_URI;
-
-	@BeforeClass
-	public static void startServer() throws URISyntaxException {
-		CRYPTOR.randomizeMasterKey();
-		SERVER.start();
-		SERVLET = SERVER.createServlet(TMP_VAULT.toPath(), CRYPTOR, new ArrayList<String>(), new ArrayList<String>(), "JUnitTestVault");
-		SERVLET.start();
-		VAULT_BASE_URI = new URI("http", SERVLET.getServletUri().getSchemeSpecificPart() + "/", null);
-		Assert.assertTrue(SERVLET.isRunning());
-		Assert.assertNotNull(VAULT_BASE_URI);
-	}
-
-	@AfterClass
-	public static void stopServer() {
-		SERVLET.stop();
-		SERVER.stop();
-		FileUtils.deleteQuietly(TMP_VAULT);
-	}
-
-	@Test
-	public void testFullFileDecryption() throws IOException, URISyntaxException {
-		final URL testResourceUrl = new URL(VAULT_BASE_URI.toURL(), "fullFileDecryptionTestFile.txt");
-		final HttpClient client = new HttpClient();
-
-		// prepare 64MiB test data:
-		final byte[] plaintextData = new byte[16777216 * Integer.BYTES];
-		final ByteBuffer bbIn = ByteBuffer.wrap(plaintextData);
-		for (int i = 0; i < 16777216; i++) {
-			bbIn.putInt(i);
-		}
-		final InputStream plaintextDataInputStream = new ByteArrayInputStream(plaintextData);
-
-		// put request:
-		final EntityEnclosingMethod putMethod = new PutMethod(testResourceUrl.toString());
-		putMethod.setRequestEntity(new ByteArrayRequestEntity(plaintextData));
-		final int putResponse = client.executeMethod(putMethod);
-		putMethod.releaseConnection();
-		Assert.assertEquals(201, putResponse);
-
-		// get request:
-		final HttpMethod getMethod = new GetMethod(testResourceUrl.toString());
-		final int statusCode = client.executeMethod(getMethod);
-		Assert.assertEquals(200, statusCode);
-		// final byte[] received = new byte[plaintextData.length];
-		// IOUtils.read(getMethod.getResponseBodyAsStream(), received);
-		// Assert.assertArrayEquals(plaintextData, received);
-		Assert.assertTrue(IOUtils.contentEquals(plaintextDataInputStream, getMethod.getResponseBodyAsStream()));
-		getMethod.releaseConnection();
-	}
-
-	@Test
-	public void testAsyncRangeRequests() throws IOException, URISyntaxException, InterruptedException {
-		final URL testResourceUrl = new URL(VAULT_BASE_URI.toURL(), "asyncRangeRequestTestFile.txt");
-
-		final MultiThreadedHttpConnectionManager cm = new MultiThreadedHttpConnectionManager();
-		cm.getParams().setDefaultMaxConnectionsPerHost(50);
-		final HttpClient client = new HttpClient(cm);
-
-		// prepare 8MiB test data:
-		final byte[] plaintextData = new byte[2097152 * Integer.BYTES];
-		final ByteBuffer bbIn = ByteBuffer.wrap(plaintextData);
-		for (int i = 0; i < 2097152; i++) {
-			bbIn.putInt(i);
-		}
-
-		// put request:
-		final EntityEnclosingMethod putMethod = new PutMethod(testResourceUrl.toString());
-		putMethod.setRequestEntity(new ByteArrayRequestEntity(plaintextData));
-		final int putResponse = client.executeMethod(putMethod);
-		putMethod.releaseConnection();
-		Assert.assertEquals(201, putResponse);
-
-		// multiple async range requests:
-		final List<ForkJoinTask<?>> tasks = new ArrayList<>();
-		final Random generator = new Random(System.currentTimeMillis());
-
-		final AtomicBoolean success = new AtomicBoolean(true);
-
-		// 10 full interrupted requests:
-		for (int i = 0; i < 10; i++) {
-			final ForkJoinTask<?> task = ForkJoinTask.adapt(() -> {
-				try {
-					final HttpMethod getMethod = new GetMethod(testResourceUrl.toString());
-					final int statusCode = client.executeMethod(getMethod);
-					if (statusCode != 200) {
-						LOG.error("Invalid status code for interrupted full request");
-						success.set(false);
-					}
-					getMethod.getResponseBodyAsStream().read();
-					getMethod.getResponseBodyAsStream().close();
-					getMethod.releaseConnection();
-				} catch (IOException e) {
-					throw new RuntimeException(e);
-				}
-			});
-			tasks.add(task);
-		}
-
-		// 50 crappy interrupted range requests:
-		for (int i = 0; i < 50; i++) {
-			final int lower = generator.nextInt(plaintextData.length);
-			final ForkJoinTask<?> task = ForkJoinTask.adapt(() -> {
-				try {
-					final HttpMethod getMethod = new GetMethod(testResourceUrl.toString());
-					getMethod.addRequestHeader("Range", "bytes=" + lower + "-");
-					final int statusCode = client.executeMethod(getMethod);
-					if (statusCode != 206) {
-						LOG.error("Invalid status code for interrupted range request");
-						success.set(false);
-					}
-					getMethod.getResponseBodyAsStream().read();
-					getMethod.getResponseBodyAsStream().close();
-					getMethod.releaseConnection();
-				} catch (IOException e) {
-					throw new RuntimeException(e);
-				}
-			});
-			tasks.add(task);
-		}
-
-		// 50 normal open range requests:
-		for (int i = 0; i < 50; i++) {
-			final int lower = generator.nextInt(plaintextData.length - 512);
-			final int upper = plaintextData.length - 1;
-			final ForkJoinTask<?> task = ForkJoinTask.adapt(() -> {
-				try {
-					final HttpMethod getMethod = new GetMethod(testResourceUrl.toString());
-					getMethod.addRequestHeader("Range", "bytes=" + lower + "-");
-					final byte[] expected = Arrays.copyOfRange(plaintextData, lower, upper + 1);
-					final int statusCode = client.executeMethod(getMethod);
-					final byte[] responseBody = new byte[upper - lower + 10];
-					final int bytesRead = IOUtils.read(getMethod.getResponseBodyAsStream(), responseBody);
-					getMethod.releaseConnection();
-					if (statusCode != 206) {
-						LOG.error("Invalid status code for open range request");
-						success.set(false);
-					} else if (upper - lower + 1 != bytesRead) {
-						LOG.error("Invalid response length for open range request");
-						success.set(false);
-					} else if (!Arrays.equals(expected, Arrays.copyOfRange(responseBody, 0, bytesRead))) {
-						LOG.error("Invalid response body for open range request");
-						success.set(false);
-					}
-				} catch (IOException e) {
-					throw new RuntimeException(e);
-				}
-			});
-			tasks.add(task);
-		}
-
-		// 200 normal closed range requests:
-		for (int i = 0; i < 200; i++) {
-			final int pos1 = generator.nextInt(plaintextData.length - 512);
-			final int pos2 = pos1 + 512;
-			final ForkJoinTask<?> task = ForkJoinTask.adapt(() -> {
-				try {
-					final int lower = Math.min(pos1, pos2);
-					final int upper = Math.max(pos1, pos2);
-					final HttpMethod getMethod = new GetMethod(testResourceUrl.toString());
-					getMethod.addRequestHeader("Range", "bytes=" + lower + "-" + upper);
-					final byte[] expected = Arrays.copyOfRange(plaintextData, lower, upper + 1);
-					final int statusCode = client.executeMethod(getMethod);
-					final byte[] responseBody = new byte[upper - lower + 1];
-					final int bytesRead = IOUtils.read(getMethod.getResponseBodyAsStream(), responseBody);
-					getMethod.releaseConnection();
-					if (statusCode != 206) {
-						LOG.error("Invalid status code for closed range request");
-						success.set(false);
-					} else if (upper - lower + 1 != bytesRead) {
-						LOG.error("Invalid response length for closed range request");
-						success.set(false);
-					} else if (!Arrays.equals(expected, Arrays.copyOfRange(responseBody, 0, bytesRead))) {
-						LOG.error("Invalid response body for closed range request");
-						success.set(false);
-					}
-				} catch (IOException e) {
-					throw new RuntimeException(e);
-				}
-			});
-			tasks.add(task);
-		}
-
-		Collections.shuffle(tasks, generator);
-
-		final ForkJoinPool pool = new ForkJoinPool(4);
-		for (ForkJoinTask<?> task : tasks) {
-			pool.execute(task);
-		}
-		for (ForkJoinTask<?> task : tasks) {
-			task.join();
-		}
-		pool.shutdown();
-		cm.shutdown();
-
-		Assert.assertTrue(success.get());
-	}
-
-	@Test
-	public void testUnsatisfiableRangeRequest() throws IOException, URISyntaxException {
-		final URL testResourceUrl = new URL(VAULT_BASE_URI.toURL(), "unsatisfiableRangeRequestTestFile.txt");
-		final HttpClient client = new HttpClient();
-
-		// prepare file content:
-		final byte[] fileContent = "This is some test file content.".getBytes();
-
-		// put request:
-		final EntityEnclosingMethod putMethod = new PutMethod(testResourceUrl.toString());
-		putMethod.setRequestEntity(new ByteArrayRequestEntity(fileContent));
-		final int putResponse = client.executeMethod(putMethod);
-		putMethod.releaseConnection();
-		Assert.assertEquals(201, putResponse);
-
-		// get request:
-		final HttpMethod getMethod = new GetMethod(testResourceUrl.toString());
-		getMethod.addRequestHeader("Range", "chunks=1-2");
-		final int getResponse = client.executeMethod(getMethod);
-		final byte[] response = new byte[fileContent.length];
-		IOUtils.read(getMethod.getResponseBodyAsStream(), response);
-		getMethod.releaseConnection();
-		Assert.assertEquals(416, getResponse);
-		Assert.assertArrayEquals(fileContent, response);
-	}
-
-}

+ 0 - 33
main/core/src/test/resources/log4j2.xml

@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!--
-  Copyright (c) 2014 Markus Kreusch
-  This file is licensed under the terms of the MIT license.
-  See the LICENSE.txt file for more info.
-  
-  Contributors:
-      Sebastian Stenzel - log4j config for WebDAV unit tests
--->
-<Configuration status="WARN">
-
-	<Appenders>
-		<Console name="Console" target="SYSTEM_OUT">
-			<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
-			<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT" />
-		</Console>
-		<Console name="StdErr" target="SYSTEM_ERR">
-			<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
-			<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
-		</Console>
-	</Appenders>
-
-	<Loggers>
-		<!-- show our own debug messages: -->
-		<Logger name="org.cryptomator" level="DEBUG" />
-		<!-- mute dependencies: -->
-		<Root level="INFO">
-			<AppenderRef ref="Console" />
-			<AppenderRef ref="StdErr" />
-		</Root>
-	</Loggers>
-
-</Configuration>

+ 0 - 95
main/crypto-aes/pom.xml

@@ -1,95 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  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
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<parent>
-		<groupId>org.cryptomator</groupId>
-		<artifactId>main</artifactId>
-		<version>0.11.0-SNAPSHOT</version>
-	</parent>
-	<artifactId>crypto-aes</artifactId>
-	<name>Cryptomator cryptographic module (AES)</name>
-	<description>Provides stream ciphers and filename pseudonymization functions.</description>
-	
-	<properties>
-		<bouncycastle.version>1.51</bouncycastle.version>
-	</properties>
-
-	<dependencies>
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>crypto-api</artifactId>
-		</dependency>
-		
-		<!-- Bouncycastle -->
-		<dependency>
-			<groupId>org.bouncycastle</groupId>
-			<artifactId>bcprov-jdk15on</artifactId>
-			<version>${bouncycastle.version}</version>
-		</dependency>
-		
-		<!-- AES-SIV -->
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>siv-mode</artifactId>
-			<version>1.0.2</version>
-		</dependency>
-
-		<!-- Commons -->
-		<dependency>
-			<groupId>commons-io</groupId>
-			<artifactId>commons-io</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.apache.commons</groupId>
-			<artifactId>commons-collections4</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.apache.commons</groupId>
-			<artifactId>commons-lang3</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>commons-codec</groupId>
-			<artifactId>commons-codec</artifactId>
-		</dependency>
-
-		<!-- JSON -->
-		<dependency>
-			<groupId>com.fasterxml.jackson.core</groupId>
-			<artifactId>jackson-databind</artifactId>
-		</dependency>
-		
-		<!-- DI -->
-		<dependency>
-			<groupId>com.google.dagger</groupId>
-			<artifactId>dagger</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>com.google.dagger</groupId>
-			<artifactId>dagger-compiler</artifactId>
-			<scope>provided</scope>
-		</dependency>
-		
-		<!-- Tests -->
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>commons-test</artifactId>
-		</dependency>
-	</dependencies>
-	
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.jacoco</groupId>
-				<artifactId>jacoco-maven-plugin</artifactId>
-			</plugin>
-		</plugins>
-	</build>
-</project>

+ 0 - 798
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java

@@ -1,798 +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.aes256;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.SeekableByteChannel;
-import java.nio.charset.StandardCharsets;
-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.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import javax.crypto.AEADBadTagException;
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.Mac;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.ShortBufferException;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-import javax.security.auth.DestroyFailedException;
-import javax.security.auth.Destroyable;
-
-import org.bouncycastle.crypto.generators.SCrypt;
-import org.cryptomator.crypto.Cryptor;
-import org.cryptomator.crypto.exceptions.DecryptFailedException;
-import org.cryptomator.crypto.exceptions.EncryptFailedException;
-import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
-import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
-import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
-import org.cryptomator.crypto.exceptions.WrongPasswordException;
-import org.cryptomator.siv.SivMode;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration {
-
-	private static final Logger LOG = LoggerFactory.getLogger(Aes256Cryptor.class);
-
-	/**
-	 * 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
-	 * here: http://www.oracle.com/technetwork/java/javase/downloads/.
-	 */
-	private static final int AES_KEY_LENGTH_IN_BITS;
-
-	/**
-	 * SIV mode for deterministic filename encryption.
-	 */
-	private static final SivMode AES_SIV = new SivMode();
-
-	/**
-	 * PRNG for cryptographically secure random numbers. Defaults to SHA1-based number generator.
-	 * 
-	 * @see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SecureRandom
-	 */
-	private final SecureRandom securePrng;
-
-	/**
-	 * Jackson JSON-Mapper.
-	 */
-	private final ObjectMapper objectMapper = new ObjectMapper();
-
-	/**
-	 * The decrypted master key. Its lifecycle starts with the construction of an Aes256Cryptor instance or {@link #decryptMasterKey(InputStream, CharSequence)}. Its lifecycle ends with
-	 * {@link #swipeSensitiveData()}.
-	 */
-	private SecretKey primaryMasterKey;
-
-	/**
-	 * Decrypted secondary key used for hmac operations.
-	 */
-	private SecretKey hMacMasterKey;
-
-	static {
-		try {
-			final int maxKeyLength = Cipher.getMaxAllowedKeyLength(AES_KEY_ALGORITHM);
-			AES_KEY_LENGTH_IN_BITS = (maxKeyLength >= PREF_MASTER_KEY_LENGTH_IN_BITS) ? PREF_MASTER_KEY_LENGTH_IN_BITS : maxKeyLength;
-		} catch (NoSuchAlgorithmException e) {
-			throw new IllegalStateException("Algorithm should exist.", e);
-		}
-	}
-
-	/**
-	 * Creates a new Cryptor with a newly initialized PRNG.
-	 */
-
-	Aes256Cryptor(SecureRandom securePrng) {
-		this.securePrng = securePrng;
-		// No setSeed needed. See SecureRandom.getInstance(String):
-		// The first call to nextBytes will force the SecureRandom object to seed itself
-	}
-
-	@Override
-	public void randomizeMasterKey() {
-		byte[] bytes = new byte[AES_KEY_LENGTH_IN_BITS / Byte.SIZE];
-		try {
-			// No setSeed needed. See SecureRandom.getInstance(String):
-			// The first call to nextBytes will force the SecureRandom object to seed itself
-			securePrng.nextBytes(bytes);
-			this.primaryMasterKey = new SecretKeySpec(bytes, AES_KEY_ALGORITHM);
-			securePrng.nextBytes(bytes);
-			this.hMacMasterKey = new SecretKeySpec(bytes, HMAC_KEY_ALGORITHM);
-		} finally {
-			Arrays.fill(bytes, (byte) 0);
-		}
-	}
-
-	@Override
-	public void encryptMasterKey(OutputStream out, CharSequence password) throws IOException {
-		try {
-			// derive key:
-			final byte[] kekSalt = randomData(SCRYPT_SALT_LENGTH);
-			final SecretKey kek = scrypt(password, kekSalt, SCRYPT_COST_PARAM, SCRYPT_BLOCK_SIZE, AES_KEY_LENGTH_IN_BITS);
-
-			// encrypt:
-			final Cipher encCipher = aesKeyWrapCipher(kek, Cipher.WRAP_MODE);
-			byte[] wrappedPrimaryKey = encCipher.wrap(primaryMasterKey);
-			byte[] wrappedSecondaryKey = encCipher.wrap(hMacMasterKey);
-
-			// save encrypted masterkey:
-			final KeyFile keyfile = new KeyFile();
-			keyfile.setVersion(KeyFile.CURRENT_VERSION);
-			keyfile.setScryptSalt(kekSalt);
-			keyfile.setScryptCostParam(SCRYPT_COST_PARAM);
-			keyfile.setScryptBlockSize(SCRYPT_BLOCK_SIZE);
-			keyfile.setKeyLength(AES_KEY_LENGTH_IN_BITS);
-			keyfile.setPrimaryMasterKey(wrappedPrimaryKey);
-			keyfile.setHMacMasterKey(wrappedSecondaryKey);
-			objectMapper.writeValue(out, keyfile);
-		} catch (InvalidKeyException | IllegalBlockSizeException ex) {
-			throw new IllegalStateException("Invalid hard coded configuration.", ex);
-		}
-	}
-
-	@Override
-	public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException, UnsupportedVaultException {
-		try {
-			// load encrypted masterkey:
-			final KeyFile keyfile = objectMapper.readValue(in, KeyFile.class);
-
-			// check version
-			if (keyfile.getVersion() != KeyFile.CURRENT_VERSION) {
-				throw new UnsupportedVaultException(keyfile.getVersion(), KeyFile.CURRENT_VERSION);
-			}
-
-			// check, whether the key length is supported:
-			final int maxKeyLen = Cipher.getMaxAllowedKeyLength(AES_KEY_ALGORITHM);
-			if (keyfile.getKeyLength() > maxKeyLen) {
-				throw new UnsupportedKeyLengthException(keyfile.getKeyLength(), maxKeyLen);
-			}
-
-			// derive key:
-			final SecretKey kek = scrypt(password, keyfile.getScryptSalt(), keyfile.getScryptCostParam(), keyfile.getScryptBlockSize(), keyfile.getKeyLength());
-
-			// decrypt and check password by catching AEAD exception
-			final Cipher decCipher = aesKeyWrapCipher(kek, Cipher.UNWRAP_MODE);
-			SecretKey primary = (SecretKey) decCipher.unwrap(keyfile.getPrimaryMasterKey(), AES_KEY_ALGORITHM, Cipher.SECRET_KEY);
-			SecretKey secondary = (SecretKey) decCipher.unwrap(keyfile.getHMacMasterKey(), HMAC_KEY_ALGORITHM, Cipher.SECRET_KEY);
-
-			// everything ok, assign decrypted keys:
-			this.primaryMasterKey = primary;
-			this.hMacMasterKey = secondary;
-		} catch (NoSuchAlgorithmException ex) {
-			throw new IllegalStateException("Algorithm should exist.", ex);
-		} catch (InvalidKeyException e) {
-			throw new WrongPasswordException();
-		}
-	}
-
-	@Override
-	public boolean isDestroyed() {
-		if (primaryMasterKey == null || hMacMasterKey == null) {
-			// master keys have not been set yet, so there is nothing sensitive
-			return true;
-		}
-		return primaryMasterKey.isDestroyed() && hMacMasterKey.isDestroyed();
-	}
-
-	@Override
-	public void destroy() {
-		destroyQuietly(primaryMasterKey);
-		destroyQuietly(hMacMasterKey);
-	}
-
-	private void destroyQuietly(Destroyable d) {
-		try {
-			d.destroy();
-		} catch (DestroyFailedException e) {
-			// ignore
-		}
-	}
-
-	private Cipher aesKeyWrapCipher(SecretKey key, int cipherMode) {
-		try {
-			final Cipher cipher = Cipher.getInstance(AES_KEYWRAP_CIPHER);
-			cipher.init(cipherMode, key);
-			return cipher;
-		} catch (InvalidKeyException ex) {
-			throw new IllegalArgumentException("Invalid key.", ex);
-		} catch (NoSuchAlgorithmException | NoSuchPaddingException ex) {
-			throw new IllegalStateException("Algorithm/Padding should exist.", ex);
-		}
-	}
-
-	private Cipher aesCtrCipher(SecretKey key, byte[] iv, int cipherMode) {
-		try {
-			final Cipher cipher = Cipher.getInstance(AES_CTR_CIPHER);
-			cipher.init(cipherMode, key, new IvParameterSpec(iv));
-			return cipher;
-		} catch (InvalidKeyException ex) {
-			throw new IllegalArgumentException("Invalid key.", ex);
-		} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException ex) {
-			throw new IllegalStateException("Algorithm/Padding should exist and accept an IV.", ex);
-		}
-	}
-
-	private Cipher aesCbcCipher(SecretKey key, byte[] iv, int cipherMode) {
-		try {
-			final Cipher cipher = Cipher.getInstance(AES_CBC_CIPHER);
-			cipher.init(cipherMode, key, new IvParameterSpec(iv));
-			return cipher;
-		} catch (InvalidKeyException ex) {
-			throw new IllegalArgumentException("Invalid key.", ex);
-		} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException ex) {
-			throw new AssertionError("Every implementation of the Java platform is required to support AES/CBC/PKCS5Padding, which accepts an IV", ex);
-		}
-	}
-
-	private Mac hmacSha256(SecretKey key) {
-		try {
-			final Mac mac = Mac.getInstance(HMAC_KEY_ALGORITHM);
-			mac.init(key);
-			return mac;
-		} catch (NoSuchAlgorithmException e) {
-			throw new AssertionError("Every implementation of the Java platform is required to support HmacSHA256.", e);
-		} catch (InvalidKeyException e) {
-			throw new IllegalArgumentException("Invalid key", e);
-		}
-	}
-
-	private MessageDigest sha256() {
-		try {
-			return MessageDigest.getInstance("SHA-256");
-		} catch (NoSuchAlgorithmException e) {
-			throw new AssertionError("Every implementation of the Java platform is required to support Sha-256");
-		}
-	}
-
-	private byte[] randomData(int length) {
-		final byte[] result = new byte[length];
-		securePrng.nextBytes(result);
-		return result;
-	}
-
-	private SecretKey scrypt(CharSequence password, byte[] salt, int costParam, int blockSize, int keyLengthInBits) {
-		// use sb, as password.toString's implementation is unknown
-		final StringBuilder sb = new StringBuilder(password);
-		final byte[] pw = sb.toString().getBytes();
-		try {
-			final byte[] key = SCrypt.generate(pw, salt, costParam, blockSize, 1, keyLengthInBits / Byte.SIZE);
-			return new SecretKeySpec(key, AES_KEY_ALGORITHM);
-		} finally {
-			// destroy copied bytes of the plaintext password:
-			Arrays.fill(pw, (byte) 0);
-			for (int i = 0; i < password.length(); i++) {
-				sb.setCharAt(i, (char) 0);
-			}
-		}
-	}
-
-	@Override
-	public String encryptDirectoryPath(String cleartextDirectoryId, String nativePathSep) {
-		final byte[] cleartextBytes = cleartextDirectoryId.getBytes(StandardCharsets.UTF_8);
-		byte[] encryptedBytes = AES_SIV.encrypt(primaryMasterKey, hMacMasterKey, cleartextBytes);
-		final byte[] hashed = sha256().digest(encryptedBytes);
-		final String encryptedThenHashedPath = ENCRYPTED_FILENAME_CODEC.encodeAsString(hashed);
-		return encryptedThenHashedPath.substring(0, 2) + nativePathSep + encryptedThenHashedPath.substring(2);
-	}
-
-	@Override
-	public String encryptFilename(String cleartextName) {
-		final byte[] cleartextBytes = cleartextName.getBytes(StandardCharsets.UTF_8);
-		final byte[] encryptedBytes = AES_SIV.encrypt(primaryMasterKey, hMacMasterKey, cleartextBytes);
-		return ENCRYPTED_FILENAME_CODEC.encodeAsString(encryptedBytes);
-	}
-
-	@Override
-	public String decryptFilename(String ciphertextName) throws DecryptFailedException {
-		final byte[] encryptedBytes = ENCRYPTED_FILENAME_CODEC.decode(ciphertextName);
-		try {
-			final byte[] cleartextBytes = AES_SIV.decrypt(primaryMasterKey, hMacMasterKey, encryptedBytes);
-			return new String(cleartextBytes, StandardCharsets.UTF_8);
-		} catch (AEADBadTagException e) {
-			throw new DecryptFailedException(e);
-		}
-	}
-
-	@Override
-	public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException, MacAuthenticationFailedException {
-		// read header:
-		encryptedFile.position(0);
-		final ByteBuffer headerBuf = ByteBuffer.allocate(104);
-		final int headerBytesRead = readFromChannel(encryptedFile, headerBuf);
-		if (headerBytesRead != headerBuf.capacity()) {
-			return null;
-		}
-
-		// read iv:
-		final byte[] iv = new byte[AES_BLOCK_LENGTH];
-		headerBuf.position(0);
-		headerBuf.get(iv);
-
-		// read sensitive header data:
-		final byte[] encryptedSensitiveHeaderContentBytes = new byte[48];
-		headerBuf.position(24);
-		headerBuf.get(encryptedSensitiveHeaderContentBytes);
-
-		// read stored header mac:
-		final byte[] storedHeaderMac = new byte[32];
-		headerBuf.position(72);
-		headerBuf.get(storedHeaderMac);
-
-		// calculate mac over first 72 bytes of header:
-		final Mac headerMac = this.hmacSha256(hMacMasterKey);
-		headerBuf.rewind();
-		headerBuf.limit(72);
-		headerMac.update(headerBuf);
-
-		final boolean macMatches = MessageDigest.isEqual(storedHeaderMac, headerMac.doFinal());
-		if (!macMatches) {
-			throw new MacAuthenticationFailedException("MAC authentication failed.");
-		}
-
-		// decrypt sensitive header data:
-		final byte[] decryptedSensitiveHeaderContentBytes = decryptHeaderData(encryptedSensitiveHeaderContentBytes, iv);
-		final ByteBuffer sensitiveHeaderContentBuf = ByteBuffer.wrap(decryptedSensitiveHeaderContentBytes);
-		final Long fileSize = sensitiveHeaderContentBuf.getLong();
-
-		return fileSize;
-	}
-
-	private byte[] decryptHeaderData(byte[] ciphertextBytes, byte[] iv) {
-		try {
-			final Cipher sizeCipher = aesCbcCipher(primaryMasterKey, iv, Cipher.DECRYPT_MODE);
-			return sizeCipher.doFinal(ciphertextBytes);
-		} catch (IllegalBlockSizeException | BadPaddingException e) {
-			throw new IllegalStateException(e);
-		}
-	}
-
-	private byte[] encryptHeaderData(byte[] plaintextBytes, byte[] iv) {
-		try {
-			final Cipher sizeCipher = aesCbcCipher(primaryMasterKey, iv, Cipher.ENCRYPT_MODE);
-			return sizeCipher.doFinal(plaintextBytes);
-		} catch (IllegalBlockSizeException | BadPaddingException e) {
-			throw new IllegalStateException("Block size must be valid, as padding is requested. BadPaddingException not possible in encrypt mode.", e);
-		}
-	}
-
-	@Override
-	public Long decryptFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile, boolean authenticate) throws IOException, DecryptFailedException {
-		// read header:
-		encryptedFile.position(0l);
-		final ByteBuffer headerBuf = ByteBuffer.allocate(104);
-		final int headerBytesRead = readFromChannel(encryptedFile, headerBuf);
-		if (headerBytesRead != headerBuf.capacity()) {
-			throw new IOException("Failed to read file header.");
-		}
-
-		// read iv:
-		final byte[] iv = new byte[AES_BLOCK_LENGTH];
-		headerBuf.position(0);
-		headerBuf.get(iv);
-
-		// read nonce:
-		final byte[] nonce = new byte[8];
-		headerBuf.position(16);
-		headerBuf.get(nonce);
-
-		// read sensitive header data:
-		final byte[] encryptedSensitiveHeaderContentBytes = new byte[48];
-		headerBuf.position(24);
-		headerBuf.get(encryptedSensitiveHeaderContentBytes);
-
-		// read header mac:
-		final byte[] storedHeaderMac = new byte[32];
-		headerBuf.position(72);
-		headerBuf.get(storedHeaderMac);
-
-		// calculate mac over first 72 bytes of header:
-		if (authenticate) {
-			final Mac headerMac = this.hmacSha256(hMacMasterKey);
-			headerBuf.position(0);
-			headerBuf.limit(72);
-			headerMac.update(headerBuf);
-			if (!MessageDigest.isEqual(storedHeaderMac, headerMac.doFinal())) {
-				throw new MacAuthenticationFailedException("Header MAC authentication failed.");
-			}
-		}
-
-		// decrypt sensitive header data:
-		final byte[] fileKeyBytes = new byte[32];
-		final byte[] decryptedSensitiveHeaderContentBytes = decryptHeaderData(encryptedSensitiveHeaderContentBytes, iv);
-		final ByteBuffer sensitiveHeaderContentBuf = ByteBuffer.wrap(decryptedSensitiveHeaderContentBytes);
-		final Long fileSize = sensitiveHeaderContentBuf.getLong();
-		sensitiveHeaderContentBuf.get(fileKeyBytes);
-
-		// prepare content decryption:
-		final SecretKey fileKey = new SecretKeySpec(fileKeyBytes, AES_KEY_ALGORITHM);
-		final LengthLimitingOutputStream paddingRemovingOutputStream = new LengthLimitingOutputStream(plaintextFile, fileSize);
-		final CryptoWorkerExecutor executor = new CryptoWorkerExecutor(Runtime.getRuntime().availableProcessors(), (lock, blockDone, currentBlock, inputQueue) -> {
-			return new DecryptWorker(lock, blockDone, currentBlock, inputQueue, authenticate, Channels.newChannel(paddingRemovingOutputStream)) {
-
-				@Override
-				protected Cipher initCipher(long startBlockNum) {
-					final ByteBuffer nonceAndCounterBuf = ByteBuffer.allocate(AES_BLOCK_LENGTH);
-					nonceAndCounterBuf.put(nonce);
-					nonceAndCounterBuf.putLong(startBlockNum * CONTENT_MAC_BLOCK / AES_BLOCK_LENGTH);
-					final byte[] nonceAndCounter = nonceAndCounterBuf.array();
-					return aesCtrCipher(fileKey, nonceAndCounter, Cipher.DECRYPT_MODE);
-				}
-
-				@Override
-				protected Mac initMac() {
-					return hmacSha256(hMacMasterKey);
-				}
-
-				@Override
-				protected void checkMac(Mac mac, long blockNum, ByteBuffer ciphertextBuf, ByteBuffer macBuf) throws MacAuthenticationFailedException {
-					mac.update(iv);
-					mac.update(longToByteArray(blockNum));
-					mac.update(ciphertextBuf);
-					final byte[] calculatedMac = mac.doFinal();
-					final byte[] storedMac = new byte[mac.getMacLength()];
-					macBuf.get(storedMac);
-					if (!MessageDigest.isEqual(calculatedMac, storedMac)) {
-						throw new MacAuthenticationFailedException("Content MAC authentication failed.");
-					}
-				}
-
-				@Override
-				protected void decrypt(Cipher cipher, ByteBuffer ciphertextBuf, ByteBuffer plaintextBuf) throws DecryptFailedException {
-					assert plaintextBuf.remaining() >= cipher.getOutputSize(ciphertextBuf.remaining());
-					try {
-						cipher.update(ciphertextBuf, plaintextBuf);
-					} catch (ShortBufferException e) {
-						throw new DecryptFailedException(e);
-					}
-				}
-
-			};
-		});
-
-		// read as many blocks from file as possible, but wait if queue is full:
-		encryptedFile.position(104l);
-		final int maxNumBlocks = 64;
-		int numBlocks = 1;
-		int bytesRead = 0;
-		long blockNumber = 0;
-		do {
-			if (numBlocks < maxNumBlocks) {
-				numBlocks++;
-			}
-			final int inBufSize = numBlocks * (CONTENT_MAC_BLOCK + 32);
-			final ByteBuffer buf = ByteBuffer.allocate(inBufSize);
-			bytesRead = readFromChannel(encryptedFile, buf);
-			buf.flip();
-			final int blocksRead = (int) Math.ceil(bytesRead / (double) (CONTENT_MAC_BLOCK + 32));
-			final boolean consumedInTime = executor.offer(new BlocksData(buf.asReadOnlyBuffer(), blockNumber, blocksRead), 1, TimeUnit.SECONDS);
-			if (!consumedInTime) {
-				break;
-			}
-			blockNumber += numBlocks;
-		} while (bytesRead == numBlocks * (CONTENT_MAC_BLOCK + 32));
-
-		// wait for decryption workers to finish:
-		try {
-			executor.waitUntilDone(1, TimeUnit.SECONDS);
-		} catch (ExecutionException e) {
-			Throwable cause = e;
-			while (cause instanceof ExecutionException) {
-				cause = cause.getCause();
-			}
-			if (cause instanceof IOException) {
-				throw (IOException) cause;
-			} else if (cause instanceof TimeoutException) {
-				throw new DecryptFailedException(cause);
-			} else if (cause instanceof RuntimeException) {
-				throw (RuntimeException) cause;
-			} else {
-				LOG.error("Unexpected exception", e);
-				throw new RuntimeException(cause);
-			}
-		} finally {
-			destroyQuietly(fileKey);
-		}
-
-		return paddingRemovingOutputStream.getBytesWritten();
-	}
-
-	@Override
-	public Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length, boolean authenticate) throws IOException, DecryptFailedException {
-		// read header:
-		encryptedFile.position(0l);
-		final ByteBuffer headerBuf = ByteBuffer.allocate(104);
-		final int headerBytesRead = readFromChannel(encryptedFile, headerBuf);
-		if (headerBytesRead != headerBuf.capacity()) {
-			throw new IOException("Failed to read file header.");
-		}
-
-		// read iv:
-		final byte[] iv = new byte[AES_BLOCK_LENGTH];
-		headerBuf.position(0);
-		headerBuf.get(iv);
-
-		// read nonce:
-		final byte[] nonce = new byte[8];
-		headerBuf.position(16);
-		headerBuf.get(nonce);
-
-		// read sensitive header data:
-		final byte[] encryptedSensitiveHeaderContentBytes = new byte[48];
-		headerBuf.position(24);
-		headerBuf.get(encryptedSensitiveHeaderContentBytes);
-
-		// read header mac:
-		final byte[] storedHeaderMac = new byte[32];
-		headerBuf.position(72);
-		headerBuf.get(storedHeaderMac);
-
-		// calculate mac over first 72 bytes of header:
-		if (authenticate) {
-			final Mac headerMac = this.hmacSha256(hMacMasterKey);
-			headerBuf.position(0);
-			headerBuf.limit(72);
-			headerMac.update(headerBuf);
-			if (!MessageDigest.isEqual(storedHeaderMac, headerMac.doFinal())) {
-				throw new MacAuthenticationFailedException("Header MAC authentication failed.");
-			}
-		}
-
-		// decrypt sensitive header data:
-		final byte[] fileKeyBytes = new byte[32];
-		final byte[] decryptedSensitiveHeaderContentBytes = decryptHeaderData(encryptedSensitiveHeaderContentBytes, iv);
-		final ByteBuffer sensitiveHeaderContentBuf = ByteBuffer.wrap(decryptedSensitiveHeaderContentBytes);
-		final Long fileSize = sensitiveHeaderContentBuf.getLong();
-		sensitiveHeaderContentBuf.get(fileKeyBytes);
-
-		assert pos + length - 1 < fileSize;
-
-		// find first relevant block:
-		final long startBlock = pos / CONTENT_MAC_BLOCK; // floor
-		final long startByte = startBlock * (CONTENT_MAC_BLOCK + 32) + 104;
-		final long offsetFromFirstBlock = pos - startBlock * CONTENT_MAC_BLOCK;
-
-		// append correct counter value to nonce:
-		final ByteBuffer nonceAndCounterBuf = ByteBuffer.allocate(AES_BLOCK_LENGTH);
-		nonceAndCounterBuf.put(nonce);
-		nonceAndCounterBuf.putLong(startBlock * CONTENT_MAC_BLOCK / AES_BLOCK_LENGTH);
-		final byte[] nonceAndCounter = nonceAndCounterBuf.array();
-
-		// content decryption:
-		encryptedFile.position(startByte);
-		final SecretKey fileKey = new SecretKeySpec(fileKeyBytes, AES_KEY_ALGORITHM);
-		final Cipher cipher = this.aesCtrCipher(fileKey, nonceAndCounter, Cipher.DECRYPT_MODE);
-		final Mac contentMac = this.hmacSha256(hMacMasterKey);
-
-		try {
-			// reading ciphered input and MACs interleaved:
-			long bytesWritten = 0;
-			final ByteBuffer buf = ByteBuffer.allocate(CONTENT_MAC_BLOCK + 32);
-			int n = 0;
-			long blockNum = startBlock;
-			while ((n = readFromChannel(encryptedFile, buf)) > 0 && bytesWritten < length) {
-				if (n < 32) {
-					throw new DecryptFailedException("Invalid file content, missing MAC.");
-				}
-
-				buf.flip();
-				final ByteBuffer ciphertextBuf = buf.asReadOnlyBuffer();
-				ciphertextBuf.limit(n - 32);
-
-				// check MAC of current block:
-				if (authenticate) {
-					final byte[] storedMac = new byte[contentMac.getMacLength()];
-					final ByteBuffer storedMacBuf = buf.asReadOnlyBuffer();
-					storedMacBuf.position(n - 32);
-					storedMacBuf.get(storedMac);
-					contentMac.update(iv);
-					contentMac.update(longToByteArray(blockNum));
-					contentMac.update(ciphertextBuf);
-					ciphertextBuf.rewind();
-					final byte[] calculatedMac = contentMac.doFinal();
-					if (!MessageDigest.isEqual(calculatedMac, storedMac)) {
-						throw new MacAuthenticationFailedException("Content MAC authentication failed.");
-					}
-				}
-
-				// decrypt block:
-				final ByteBuffer plaintextBuf = ByteBuffer.allocate(cipher.getOutputSize(ciphertextBuf.remaining()));
-				cipher.update(ciphertextBuf, plaintextBuf);
-				plaintextBuf.flip();
-				final int offset = (bytesWritten == 0) ? (int) offsetFromFirstBlock : 0;
-				final long pending = length - bytesWritten;
-				final int available = plaintextBuf.remaining() - offset;
-				final int currentBatch = (int) Math.min(pending, available);
-
-				plaintextFile.write(plaintextBuf.array(), offset, currentBatch);
-				bytesWritten += currentBatch;
-				blockNum++;
-				buf.rewind();
-			}
-			return bytesWritten;
-		} catch (ShortBufferException e) {
-			throw new IllegalStateException("Output buffer size known to fit.", e);
-		} finally {
-			destroyQuietly(fileKey);
-		}
-	}
-
-	/**
-	 * header = {16 byte iv, 8 byte nonce, 48 byte sensitive header data (file size + file key + padding), 32 byte headerMac}
-	 */
-	@Override
-	public Long encryptFile(InputStream plaintextFile, SeekableByteChannel encryptedFile) throws IOException, EncryptFailedException {
-		// truncate file
-		encryptedFile.truncate(0l);
-
-		// choose a random header IV:
-		final byte[] iv = randomData(AES_BLOCK_LENGTH);
-
-		// chosse 8 byte random nonce and 8 byte counter set to zero:
-		final byte[] nonce = randomData(8);
-
-		// choose a random content key:
-		final byte[] fileKeyBytes = randomData(32);
-
-		// 104 byte header buffer (16 header IV, 8 content nonce, 48 sensitive header data, 32 headerMac), filled after writing the content
-		final ByteBuffer headerBuf = ByteBuffer.allocate(104);
-		headerBuf.limit(104);
-		encryptedFile.write(headerBuf);
-
-		// prepare content encryption:
-		final SecretKey fileKey = new SecretKeySpec(fileKeyBytes, AES_KEY_ALGORITHM);
-		final CryptoWorkerExecutor executor = new CryptoWorkerExecutor(Runtime.getRuntime().availableProcessors(), (lock, blockDone, currentBlock, inputQueue) -> {
-			return new EncryptWorker(lock, blockDone, currentBlock, inputQueue, encryptedFile) {
-
-				@Override
-				protected Cipher initCipher(long startBlockNum) {
-					final ByteBuffer nonceAndCounterBuf = ByteBuffer.allocate(AES_BLOCK_LENGTH);
-					nonceAndCounterBuf.put(nonce);
-					nonceAndCounterBuf.putLong(startBlockNum * CONTENT_MAC_BLOCK / AES_BLOCK_LENGTH);
-					final byte[] nonceAndCounter = nonceAndCounterBuf.array();
-					return aesCtrCipher(fileKey, nonceAndCounter, Cipher.ENCRYPT_MODE);
-				}
-
-				@Override
-				protected Mac initMac() {
-					return hmacSha256(hMacMasterKey);
-				}
-
-				@Override
-				protected byte[] calcMac(Mac mac, long blockNum, ByteBuffer ciphertextBuf) {
-					mac.update(iv);
-					mac.update(longToByteArray(blockNum));
-					mac.update(ciphertextBuf);
-					return mac.doFinal();
-				}
-
-				@Override
-				protected void encrypt(Cipher cipher, ByteBuffer plaintextBuf, ByteBuffer ciphertextBuf) throws EncryptFailedException {
-					try {
-						assert ciphertextBuf.remaining() >= cipher.getOutputSize(plaintextBuf.remaining());
-						cipher.update(plaintextBuf, ciphertextBuf);
-					} catch (ShortBufferException e) {
-						throw new EncryptFailedException(e);
-					}
-				}
-			};
-		});
-
-		// read as many blocks from file as possible, but wait if queue is full:
-		final byte[] randomPadding = this.randomData(AES_BLOCK_LENGTH);
-		final LengthObfuscatingInputStream in = new LengthObfuscatingInputStream(plaintextFile, randomPadding);
-		final ReadableByteChannel channel = Channels.newChannel(in);
-		int bytesRead = 0;
-		long blockNumber = 0;
-		final int maxNumBlocks = 64;
-		int numBlocks = 0;
-		do {
-			if (numBlocks < maxNumBlocks) {
-				numBlocks++;
-			}
-			final int inBufSize = numBlocks * CONTENT_MAC_BLOCK;
-			final ByteBuffer inBuf = ByteBuffer.allocate(inBufSize);
-			bytesRead = readFromChannel(channel, inBuf);
-			inBuf.flip();
-			final int blocksRead = (int) Math.ceil(bytesRead / (double) CONTENT_MAC_BLOCK);
-			final boolean consumedInTime = executor.offer(new BlocksData(inBuf.asReadOnlyBuffer(), blockNumber, blocksRead), 1, TimeUnit.SECONDS);
-			if (!consumedInTime) {
-				break;
-			}
-			blockNumber += numBlocks;
-		} while (bytesRead == numBlocks * CONTENT_MAC_BLOCK);
-
-		// wait for encryption workers to finish:
-		try {
-			executor.waitUntilDone(1, TimeUnit.SECONDS);
-		} catch (ExecutionException e) {
-			Throwable cause = e;
-			while (cause instanceof ExecutionException) {
-				cause = cause.getCause();
-			}
-			if (cause instanceof IOException) {
-				throw (IOException) cause;
-			} else if (cause instanceof TimeoutException) {
-				throw new EncryptFailedException(cause);
-			} else if (cause instanceof RuntimeException) {
-				throw (RuntimeException) cause;
-			} else {
-				LOG.error("Unexpected exception", e);
-				throw new RuntimeException(cause);
-			}
-		} finally {
-			destroyQuietly(fileKey);
-		}
-
-		// create and write header:
-		final long plaintextSize = in.getRealInputLength();
-		final ByteBuffer sensitiveHeaderContentBuf = ByteBuffer.allocate(Long.BYTES + fileKeyBytes.length);
-		sensitiveHeaderContentBuf.putLong(plaintextSize);
-		sensitiveHeaderContentBuf.put(fileKeyBytes);
-		headerBuf.clear();
-		headerBuf.put(iv);
-		headerBuf.put(nonce);
-		headerBuf.put(encryptHeaderData(sensitiveHeaderContentBuf.array(), iv));
-		headerBuf.flip();
-		final Mac headerMac = this.hmacSha256(hMacMasterKey);
-		headerMac.update(headerBuf);
-		headerBuf.limit(104);
-		headerBuf.put(headerMac.doFinal());
-		headerBuf.flip();
-		encryptedFile.position(0);
-		encryptedFile.write(headerBuf);
-
-		return plaintextSize;
-	}
-
-	private byte[] longToByteArray(long lng) {
-		return ByteBuffer.allocate(Long.SIZE / Byte.SIZE).putLong(lng).array();
-	}
-
-	/**
-	 * Reads bytes from a ReadableByteChannel.
-	 * <p>
-	 * This implementation guarantees that it will read as many bytes
-	 * as possible before giving up; this may not always be the case for
-	 * subclasses of {@link ReadableByteChannel}.
-	 *
-	 * @param input the byte channel to read
-	 * @param buffer byte buffer destination
-	 * @return the actual length read; may be less than requested if EOF was reached
-	 * @throws IOException if a read error occurs
-	 * @see
-	 * 		<a href="http://commons.apache.org/proper/commons-io/apidocs/src-html/org/apache/commons/io/IOUtils.html">Apache Commons IOUtils 2.5</a>
-	 */
-	public static int readFromChannel(final ReadableByteChannel input, final ByteBuffer buffer) throws IOException {
-		final int length = buffer.remaining();
-		while (buffer.remaining() > 0) {
-			final int count = input.read(buffer);
-			if (count == -1) { // EOF
-				break;
-			}
-		}
-		return length - buffer.remaining();
-	}
-
-}

+ 0 - 89
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java

@@ -1,89 +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.aes256;
-
-import org.apache.commons.codec.binary.Base32;
-import org.apache.commons.codec.binary.BaseNCodec;
-
-interface AesCryptographicConfiguration {
-
-	/**
-	 * Number of bytes used as salt, where needed.
-	 */
-	int SCRYPT_SALT_LENGTH = 8;
-
-	/**
-	 * Scrypt CPU/Memory cost parameter.
-	 */
-	int SCRYPT_COST_PARAM = 1 << 14;
-
-	/**
-	 * Scrypt block size (affects memory consumption)
-	 */
-	int SCRYPT_BLOCK_SIZE = 8;
-
-	/**
-	 * Preferred number of bytes of the master key.
-	 */
-	int PREF_MASTER_KEY_LENGTH_IN_BITS = 256;
-
-	/**
-	 * Number of bytes used as seed for the PRNG.
-	 */
-	int PRNG_SEED_LENGTH = 16;
-
-	/**
-	 * Algorithm used for en/decryption.
-	 * 
-	 * @see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#AlgorithmParameters
-	 */
-	String AES_KEY_ALGORITHM = "AES";
-
-	/**
-	 * Key algorithm for keyed MAC.
-	 */
-	String HMAC_KEY_ALGORITHM = "HmacSHA256";
-
-	/**
-	 * Cipher specs for RFC 3394 masterkey encryption.
-	 * 
-	 * @see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Cipher
-	 */
-	String AES_KEYWRAP_CIPHER = "AESWrap";
-
-	/**
-	 * Cipher specs for file content encryption. Using CTR-mode for random access.<br/>
-	 * 
-	 * @see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Cipher
-	 */
-	String AES_CTR_CIPHER = "AES/CTR/NoPadding";
-
-	/**
-	 * Cipher specs for file header encryption (fixed-length block cipher).<br/>
-	 * 
-	 * @see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#impl
-	 */
-	String AES_CBC_CIPHER = "AES/CBC/PKCS5Padding";
-
-	/**
-	 * AES block size is 128 bit or 16 bytes.
-	 */
-	int AES_BLOCK_LENGTH = 16;
-
-	/**
-	 * Number of bytes, a content block over which a MAC is calculated consists of.
-	 */
-	int CONTENT_MAC_BLOCK = 32 * 1024;
-
-	/**
-	 * How to encode the encrypted file names safely. Base32 uses only alphanumeric characters and is case-insensitive.
-	 */
-	BaseNCodec ENCRYPTED_FILENAME_CODEC = new Base32();
-
-}

+ 0 - 22
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/BlocksData.java

@@ -1,22 +0,0 @@
-package org.cryptomator.crypto.aes256;
-
-import java.nio.ByteBuffer;
-
-class BlocksData {
-
-	public static final int MAX_NUM_BLOCKS = 128;
-
-	final ByteBuffer buffer;
-	final long startBlockNum;
-	final int numBlocks;
-
-	BlocksData(ByteBuffer buffer, long startBlockNum, int numBlocks) {
-		if (numBlocks > MAX_NUM_BLOCKS) {
-			throw new IllegalArgumentException("Too many blocks to process at once: " + numBlocks);
-		}
-		this.buffer = buffer;
-		this.startBlockNum = startBlockNum;
-		this.numBlocks = numBlocks;
-	}
-
-}

+ 0 - 15
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoComponent.java

@@ -1,15 +0,0 @@
-package org.cryptomator.crypto.aes256;
-
-import javax.inject.Singleton;
-
-import org.cryptomator.crypto.Cryptor;
-
-import dagger.Component;
-
-@Singleton
-@Component(modules = CryptoModule.class)
-interface CryptoComponent {
-
-	Cryptor cryptor();
-
-}

+ 0 - 29
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoModule.java

@@ -1,29 +0,0 @@
-package org.cryptomator.crypto.aes256;
-
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-
-import org.cryptomator.crypto.Cryptor;
-
-import dagger.Module;
-import dagger.Provides;
-
-@Module
-public class CryptoModule {
-
-	@Provides
-	SecureRandom provideRandomNumberGenerator() {
-		try {
-			return SecureRandom.getInstanceStrong();
-		} catch (NoSuchAlgorithmException e) {
-			// quote "Every implementation of the Java platform is required to support at least one strong SecureRandom implementation."
-			throw new AssertionError("No SecureRandom implementation available.");
-		}
-	}
-
-	@Provides
-	public Cryptor provideCryptor(SecureRandom secureRandom) {
-		return new Aes256Cryptor(secureRandom);
-	}
-
-}

+ 0 - 68
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoWorker.java

@@ -1,68 +0,0 @@
-package org.cryptomator.crypto.aes256;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-
-import org.cryptomator.crypto.exceptions.CryptingException;
-
-abstract class CryptoWorker implements Callable<Void> {
-
-	static final BlocksData POISON = new BlocksData(ByteBuffer.allocate(0), -1L, 0);
-
-	private final Lock lock;
-	private final Condition blockDone;
-	private final AtomicLong currentBlock;
-	private final BlockingQueue<BlocksData> queue;
-
-	public CryptoWorker(Lock lock, Condition blockDone, AtomicLong currentBlock, BlockingQueue<BlocksData> queue) {
-		this.lock = lock;
-		this.blockDone = blockDone;
-		this.currentBlock = currentBlock;
-		this.queue = queue;
-	}
-
-	@Override
-	public final Void call() throws IOException, TimeoutException {
-		try {
-			while (!Thread.currentThread().isInterrupted()) {
-				final BlocksData blocksData = queue.take();
-				if (blocksData == POISON) {
-					break;
-				}
-				final ByteBuffer processedBytes = this.process(blocksData);
-				lock.lock();
-				try {
-					while (currentBlock.get() != blocksData.startBlockNum) {
-						if (!blockDone.await(1, TimeUnit.SECONDS)) {
-							throw new TimeoutException("Waited too long to write block " + blocksData.startBlockNum + "; Current block " + currentBlock.get());
-						}
-					}
-					assert currentBlock.get() == blocksData.startBlockNum;
-					// yay, its my turn!
-					this.write(processedBytes);
-					// signal worker working on next block:
-					currentBlock.set(blocksData.startBlockNum + blocksData.numBlocks);
-					blockDone.signalAll();
-				} finally {
-					lock.unlock();
-				}
-			}
-		} catch (InterruptedException e) {
-			// will happen for executorService.shutdownNow() or future.cancel()
-			Thread.currentThread().interrupt();
-		}
-		return null;
-	}
-
-	protected abstract ByteBuffer process(BlocksData block) throws CryptingException;
-
-	protected abstract void write(ByteBuffer processedBytes) throws IOException;
-
-}

+ 0 - 167
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoWorkerExecutor.java

@@ -1,167 +0,0 @@
-package org.cryptomator.crypto.aes256;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.CompletionService;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorCompletionService;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-class CryptoWorkerExecutor {
-
-	private static final Logger LOG = LoggerFactory.getLogger(CryptoWorkerExecutor.class);
-
-	private final int numWorkers;
-	private final Lock lock;
-	private final Condition blockDone;
-	private final AtomicLong currentBlock;
-	private final BlockingQueue<BlocksData> inputQueue;
-	private final ExecutorService executorService;
-	private final Future<Void> allWork;
-
-	/**
-	 * Starts as many {@link CryptoWorker} as specified in the constructor, that start working immediately on the items submitted via {@link #offer(BlocksData, long, TimeUnit)}.
-	 */
-	public CryptoWorkerExecutor(int numWorkers, WorkerFactory workerFactory) {
-		this.numWorkers = numWorkers;
-		this.lock = new ReentrantLock();
-		this.blockDone = lock.newCondition();
-		this.currentBlock = new AtomicLong();
-		this.inputQueue = new LinkedBlockingQueue<>(numWorkers * 2); // one cycle read-ahead
-		this.executorService = Executors.newFixedThreadPool(numWorkers);
-
-		// start workers:
-		final CompletionService<Void> completionService = new ExecutorCompletionService<>(executorService);
-		final Collection<Future<?>> workers = new ArrayList<>(numWorkers);
-		for (int i = 0; i < numWorkers; i++) {
-			final CryptoWorker worker = workerFactory.createWorker(lock, blockDone, currentBlock, inputQueue);
-			workers.add(completionService.submit(worker));
-		}
-		final Supervisor supervisor = new Supervisor(workers, completionService);
-		this.allWork = executorService.submit(supervisor);
-	}
-
-	/**
-	 * Adds work to the work queue. On timeout all workers will be shut down.
-	 * 
-	 * @see BlockingQueue#offer(Object, long, TimeUnit)
-	 * @return <code>true</code> if the work has been added in time. <code>false</code> in any other case.
-	 */
-	public boolean offer(BlocksData data, long timeout, TimeUnit unit) {
-		if (allWork.isDone()) {
-			return false;
-		}
-		try {
-			final boolean success = inputQueue.offer(data, timeout, unit);
-			if (!success) {
-				LOG.warn("Cancelling crypto workers due to timeout. Apparently the work queue not being drained by the workers any longer.");
-				allWork.cancel(true);
-			}
-			return success;
-		} catch (InterruptedException e) {
-			LOG.error("Interrupted thread.", e);
-			executorService.shutdownNow();
-			Thread.currentThread().interrupt();
-		}
-		return false;
-	}
-
-	/**
-	 * Graceful shutdown of this executor, waiting for all jobs to finish (normally or by throwing exceptions).
-	 * 
-	 * @param timeout Maximum time spent <em>per worker</em> to wait for a graceful shutdown
-	 * @param unit Timeout unit
-	 * @throws ExecutionException If any of the workers failed.
-	 */
-	public void waitUntilDone(long timeout, TimeUnit unit) throws ExecutionException {
-		try {
-			if (allWork.isDone()) {
-				// Work is done before workers being poisoned? This will most likely throw an ExecutionException:
-				allWork.get();
-			} else if (!poisonWorkers(timeout, unit)) {
-				// Attempt to enqueue poison pill for all workers failed:
-				allWork.cancel(true);
-			} else {
-				// All poisons enqueued successfully. Now wait for termination by poison or exception:
-				allWork.get();
-			}
-		} catch (InterruptedException e) {
-			LOG.error("Interrupted thread.", e);
-			Thread.currentThread().interrupt();
-		} catch (CancellationException e) {
-			throw new ExecutionException("Work canceled", e);
-		} finally {
-			// in any case (normal or exceptional execution): shutdown executor including all workers and supervisor:
-			executorService.shutdownNow();
-		}
-	}
-
-	private boolean poisonWorkers(long timeout, TimeUnit unit) throws InterruptedException {
-		// add enough poison for each worker; each worker will consume excatly one:
-		for (int i = 0; i < numWorkers; i++) {
-			if (!inputQueue.offer(CryptoWorker.POISON, timeout, unit)) {
-				return false;
-			}
-		}
-		return true;
-	}
-
-	@FunctionalInterface
-	interface WorkerFactory {
-		CryptoWorker createWorker(Lock lock, Condition blockDone, AtomicLong currentBlock, BlockingQueue<BlocksData> inputQueue);
-	}
-
-	/**
-	 * A supervisor watches the work results of a collection of workers. The supervisor waits for all workers to finish.
-	 * The supvervisor itself does not cause any exceptions, but if <em>one</em> worker fails, all other workers are cancelled immediately and the exception propagates through this supvervisor.
-	 * Anyone waiting for the supervisor to finish will thus effectively wait for all supvervisees to finish.
-	 */
-	private static class Supervisor implements Callable<Void> {
-
-		private final Collection<Future<?>> workers;
-		private final CompletionService<?> completionService;
-
-		public Supervisor(Collection<Future<?>> workers, CompletionService<?> completionService) {
-			this.workers = workers;
-			this.completionService = completionService;
-		}
-
-		@Override
-		public Void call() throws ExecutionException {
-			try {
-				for (int i = 0; i < workers.size(); i++) {
-					try {
-						// any ExecutionException thrown here will propagate up (after work is canceled in finally block)
-						completionService.take().get();
-					} catch (CancellationException ignore) {
-					}
-				}
-			} catch (InterruptedException e) {
-				// supervisor may be interrupted when executorservice is shut down.
-				Thread.currentThread().interrupt();
-			} finally {
-				// make sure, that at the end of the day all remaining workers leave the building.
-				for (Future<?> worker : workers) {
-					worker.cancel(true);
-				}
-			}
-			// no exception up to this point -> all workers finished work normally.
-			return null;
-		}
-	}
-
-}

+ 0 - 75
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/DecryptWorker.java

@@ -1,75 +0,0 @@
-package org.cryptomator.crypto.aes256;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.WritableByteChannel;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-
-import org.cryptomator.crypto.exceptions.CryptingException;
-import org.cryptomator.crypto.exceptions.DecryptFailedException;
-import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
-
-abstract class DecryptWorker extends CryptoWorker implements AesCryptographicConfiguration {
-
-	private final boolean shouldAuthenticate;
-	private final WritableByteChannel out;
-
-	public DecryptWorker(Lock lock, Condition blockDone, AtomicLong currentBlock, BlockingQueue<BlocksData> queue, boolean shouldAuthenticate, WritableByteChannel out) {
-		super(lock, blockDone, currentBlock, queue);
-		this.shouldAuthenticate = shouldAuthenticate;
-		this.out = out;
-	}
-
-	@Override
-	protected ByteBuffer process(BlocksData data) throws CryptingException {
-		final Cipher cipher = initCipher(data.startBlockNum);
-		final Mac mac = initMac();
-
-		final ByteBuffer plaintextBuf = ByteBuffer.allocate(cipher.getOutputSize(CONTENT_MAC_BLOCK) * data.numBlocks);
-
-		final ByteBuffer ciphertextBuf = data.buffer.asReadOnlyBuffer();
-		final ByteBuffer macBuf = data.buffer.asReadOnlyBuffer();
-
-		for (long blockNum = data.startBlockNum; blockNum < data.startBlockNum + data.numBlocks; blockNum++) {
-			assert (blockNum - data.startBlockNum) < BlocksData.MAX_NUM_BLOCKS;
-			assert (blockNum - data.startBlockNum) * CONTENT_MAC_BLOCK < Integer.MAX_VALUE;
-			final int pos = (int) (blockNum - data.startBlockNum) * (CONTENT_MAC_BLOCK + mac.getMacLength());
-			ciphertextBuf.limit(Math.min(data.buffer.limit() - mac.getMacLength(), pos + CONTENT_MAC_BLOCK));
-			ciphertextBuf.position(pos);
-			try {
-				macBuf.limit(ciphertextBuf.limit() + mac.getMacLength());
-				macBuf.position(ciphertextBuf.limit());
-			} catch (IllegalArgumentException e) {
-				throw new DecryptFailedException("Invalid file content, missing MAC.");
-			}
-			if (shouldAuthenticate) {
-				checkMac(mac, blockNum, ciphertextBuf, macBuf);
-			}
-			ciphertextBuf.position(pos);
-			decrypt(cipher, ciphertextBuf, plaintextBuf);
-		}
-
-		plaintextBuf.flip();
-		return plaintextBuf;
-	}
-
-	@Override
-	protected void write(ByteBuffer processedBytes) throws IOException {
-		out.write(processedBytes);
-	}
-
-	protected abstract Cipher initCipher(long startBlockNum);
-
-	protected abstract Mac initMac();
-
-	protected abstract void checkMac(Mac mac, long blockNum, ByteBuffer ciphertextBuf, ByteBuffer macBuf) throws MacAuthenticationFailedException;
-
-	protected abstract void decrypt(Cipher cipher, ByteBuffer ciphertextBuf, ByteBuffer plaintextBuf) throws DecryptFailedException;
-
-}

+ 0 - 61
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/EncryptWorker.java

@@ -1,61 +0,0 @@
-package org.cryptomator.crypto.aes256;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.WritableByteChannel;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-
-import org.cryptomator.crypto.exceptions.CryptingException;
-import org.cryptomator.crypto.exceptions.EncryptFailedException;
-
-abstract class EncryptWorker extends CryptoWorker implements AesCryptographicConfiguration {
-
-	private final WritableByteChannel out;
-
-	public EncryptWorker(Lock lock, Condition blockDone, AtomicLong currentBlock, BlockingQueue<BlocksData> queue, WritableByteChannel out) {
-		super(lock, blockDone, currentBlock, queue);
-		this.out = out;
-	}
-
-	@Override
-	protected ByteBuffer process(BlocksData data) throws CryptingException {
-		final Cipher cipher = initCipher(data.startBlockNum);
-		final Mac mac = initMac();
-
-		final ByteBuffer ciphertextBuf = ByteBuffer.allocate((cipher.getOutputSize(CONTENT_MAC_BLOCK) + mac.getMacLength()) * data.numBlocks);
-		final ByteBuffer plaintextBuf = data.buffer.asReadOnlyBuffer();
-
-		for (long blockNum = data.startBlockNum; blockNum < data.startBlockNum + data.numBlocks; blockNum++) {
-			final int pos = (int) (blockNum - data.startBlockNum) * CONTENT_MAC_BLOCK;
-			plaintextBuf.limit(Math.min(data.buffer.limit(), pos + CONTENT_MAC_BLOCK));
-			encrypt(cipher, plaintextBuf, ciphertextBuf);
-			final ByteBuffer toMac = ciphertextBuf.asReadOnlyBuffer();
-			toMac.limit(ciphertextBuf.position());
-			toMac.position((int) (blockNum - data.startBlockNum) * (CONTENT_MAC_BLOCK + mac.getMacLength()));
-			ciphertextBuf.put(calcMac(mac, blockNum, toMac));
-		}
-
-		ciphertextBuf.flip();
-		return ciphertextBuf;
-	}
-
-	@Override
-	protected void write(ByteBuffer processedBytes) throws IOException {
-		out.write(processedBytes);
-	}
-
-	protected abstract Cipher initCipher(long startBlockNum);
-
-	protected abstract Mac initMac();
-
-	protected abstract byte[] calcMac(Mac mac, long blockNum, ByteBuffer ciphertextBuf);
-
-	protected abstract void encrypt(Cipher cipher, ByteBuffer plaintextBuf, ByteBuffer ciphertextBuf) throws EncryptFailedException;
-
-}

+ 0 - 77
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/KeyFile.java

@@ -1,77 +0,0 @@
-package org.cryptomator.crypto.aes256;
-
-import java.io.Serializable;
-
-import com.fasterxml.jackson.annotation.JsonPropertyOrder;
-
-@JsonPropertyOrder(value = {"version", "scryptSalt", "scryptCostParam", "scryptBlockSize", "keyLength", "primaryMasterKey", "hMacMasterKey"})
-public class KeyFile implements Serializable {
-
-	static final Integer CURRENT_VERSION = 2;
-	private static final long serialVersionUID = 8578363158959619885L;
-
-	private Integer version;
-	private byte[] scryptSalt;
-	private int scryptCostParam;
-	private int scryptBlockSize;
-	private int keyLength;
-	private byte[] primaryMasterKey;
-	private byte[] hMacMasterKey;
-
-	public Integer getVersion() {
-		return version;
-	}
-
-	public void setVersion(Integer version) {
-		this.version = version;
-	}
-
-	public byte[] getScryptSalt() {
-		return scryptSalt;
-	}
-
-	public void setScryptSalt(byte[] scryptSalt) {
-		this.scryptSalt = scryptSalt;
-	}
-
-	public int getScryptCostParam() {
-		return scryptCostParam;
-	}
-
-	public void setScryptCostParam(int scryptCostParam) {
-		this.scryptCostParam = scryptCostParam;
-	}
-
-	public int getScryptBlockSize() {
-		return scryptBlockSize;
-	}
-
-	public void setScryptBlockSize(int scryptBlockSize) {
-		this.scryptBlockSize = scryptBlockSize;
-	}
-
-	public int getKeyLength() {
-		return keyLength;
-	}
-
-	public void setKeyLength(int keyLength) {
-		this.keyLength = keyLength;
-	}
-
-	public byte[] getPrimaryMasterKey() {
-		return primaryMasterKey;
-	}
-
-	public void setPrimaryMasterKey(byte[] primaryMasterKey) {
-		this.primaryMasterKey = primaryMasterKey;
-	}
-
-	public byte[] getHMacMasterKey() {
-		return hMacMasterKey;
-	}
-
-	public void setHMacMasterKey(byte[] hMacMasterKey) {
-		this.hMacMasterKey = hMacMasterKey;
-	}
-
-}

+ 0 - 47
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/LengthLimitingOutputStream.java

@@ -1,47 +0,0 @@
-package org.cryptomator.crypto.aes256;
-
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-public class LengthLimitingOutputStream extends FilterOutputStream {
-
-	private final long limit;
-	private volatile long bytesWritten;
-
-	public LengthLimitingOutputStream(OutputStream out, long limit) {
-		super(out);
-		this.limit = limit;
-		this.bytesWritten = 0;
-	}
-
-	@Override
-	public void write(int b) throws IOException {
-		if (bytesWritten < limit) {
-			out.write(b);
-			increaseNumberOfWrittenBytes(1);
-		}
-	}
-
-	@Override
-	public void write(byte[] b, int off, int len) throws IOException {
-		final long bytesAvailable = limit - bytesWritten;
-		final int adjustedLen = (int) Math.min(len, bytesAvailable);
-		if (adjustedLen > 0) {
-			out.write(b, off, adjustedLen);
-			increaseNumberOfWrittenBytes(adjustedLen);
-		}
-	}
-
-	public long getBytesWritten() {
-		return bytesWritten;
-	}
-
-	private void increaseNumberOfWrittenBytes(int amount) throws IOException {
-		bytesWritten += amount;
-		if (bytesWritten >= limit) {
-			out.flush();
-		}
-	}
-
-}

+ 0 - 128
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/LengthObfuscatingInputStream.java

@@ -1,128 +0,0 @@
-package org.cryptomator.crypto.aes256;
-
-import java.io.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.apache.commons.io.IOUtils;
-
-/**
- * Not thread-safe!
- */
-public class LengthObfuscatingInputStream extends FilterInputStream {
-
-	private final byte[] padding;
-	private int paddingLength = -1;
-	private long inputBytesRead = 0;
-	private int paddingBytesRead = 0;
-
-	LengthObfuscatingInputStream(InputStream in, byte[] padding) {
-		super(in);
-		this.padding = padding;
-	}
-
-	long getRealInputLength() {
-		return inputBytesRead;
-	}
-
-	private void choosePaddingLengthOnce() {
-		if (paddingLength == -1) {
-			long upperBound = Math.min(Math.max(inputBytesRead / 10, 4096), 16 * 1024 * 1024); // 10% of original bytes (at least 4KiB), but not more than 16MiBs
-			paddingLength = (int) (Math.random() * upperBound);
-		}
-	}
-
-	@Override
-	public int read() throws IOException {
-		final int b = in.read();
-		if (b != -1) {
-			// stream available:
-			inputBytesRead++;
-			return b;
-		} else {
-			choosePaddingLengthOnce();
-			return readFromPadding();
-		}
-	}
-
-	private int readFromPadding() {
-		if (paddingLength == -1) {
-			throw new IllegalStateException("No padding length chosen yet.");
-		}
-
-		if (paddingBytesRead < paddingLength) {
-			// padding available:
-			return padding[paddingBytesRead++ % padding.length];
-		} else {
-			// end of stream AND padding
-			return -1;
-		}
-	}
-
-	@Override
-	public int read(byte[] b, int off, int len) throws IOException {
-		final int bytesRead = IOUtils.read(in, b, off, len); // 0 on EOF
-		inputBytesRead += bytesRead;
-
-		if (bytesRead == len) {
-			return bytesRead;
-		} else if (bytesRead < len) {
-			choosePaddingLengthOnce();
-			final int additionalBytesNeeded = len - bytesRead;
-			final int additionalBytesRead = readFromPadding(b, off + bytesRead, additionalBytesNeeded);
-			return (bytesRead == 0 && additionalBytesRead == 0) ? -1 : bytesRead + additionalBytesRead;
-		} else {
-			// bytesRead > len:
-			throw new IllegalStateException("Read more bytes than requested.");
-		}
-	}
-
-	/**
-	 * @return bytes read from padding (0, if fully read)
-	 */
-	private int readFromPadding(byte[] b, int off, int len) {
-		if (len < 0) {
-			throw new IllegalArgumentException("Length must not be negative");
-		}
-		if (paddingLength == -1) {
-			throw new IllegalStateException("No padding length chosen yet.");
-		}
-
-		final int remainingPadding = paddingLength - paddingBytesRead;
-		if (remainingPadding > len) {
-			// padding available:
-			for (int i = 0; i < len; i++) {
-				b[off + i] = padding[paddingBytesRead++ % padding.length];
-			}
-			return len;
-		} else {
-			// partly available:
-			for (int i = 0; i < remainingPadding; i++) {
-				b[off + i] = padding[paddingBytesRead++ % padding.length];
-			}
-			return remainingPadding;
-		}
-	}
-
-	@Override
-	public long skip(long n) throws IOException {
-		throw new IOException("Skip not supported");
-	}
-
-	@Override
-	public int available() throws IOException {
-		if (paddingLength == -1) {
-			// EOF not yet reached; delegate original stream to answer this rather complicated question:
-			return in.available();
-		} else {
-			// EOF already reached, read from remaining padding:
-			return paddingLength - paddingBytesRead;
-		}
-	}
-
-	@Override
-	public boolean markSupported() {
-		return false;
-	}
-
-}

+ 0 - 207
main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java

@@ -1,207 +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.aes256;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.channels.SeekableByteChannel;
-import java.util.Arrays;
-
-import javax.security.auth.DestroyFailedException;
-
-import org.apache.commons.io.IOUtils;
-import org.cryptomator.crypto.Cryptor;
-import org.cryptomator.crypto.exceptions.DecryptFailedException;
-import org.cryptomator.crypto.exceptions.EncryptFailedException;
-import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
-import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
-import org.cryptomator.crypto.exceptions.WrongPasswordException;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class Aes256CryptorTest {
-
-	private final Cryptor cryptor;
-
-	public Aes256CryptorTest() {
-		cryptor = DaggerCryptoTestComponent.create().cryptor();
-		cryptor.randomizeMasterKey();
-	}
-
-	@Test
-	public void testMultipleCryptorInstances() {
-		Assert.assertNotSame(DaggerCryptoTestComponent.create().cryptor(), DaggerCryptoTestComponent.create().cryptor());
-	}
-
-	@Test(timeout = 10000)
-	public void testCorrectPassword() throws IOException, WrongPasswordException, DecryptFailedException, UnsupportedKeyLengthException, DestroyFailedException, UnsupportedVaultException {
-		final String pw = "asd";
-
-		final ByteArrayOutputStream out = new ByteArrayOutputStream();
-		cryptor.encryptMasterKey(out, pw);
-		cryptor.destroy();
-
-		final Cryptor decryptor = DaggerCryptoTestComponent.create().cryptor();
-		final InputStream in = new ByteArrayInputStream(out.toByteArray());
-		decryptor.decryptMasterKey(in, pw);
-
-		IOUtils.closeQuietly(out);
-		IOUtils.closeQuietly(in);
-	}
-
-	@Test(timeout = 10000)
-	public void testWrongPassword() throws IOException, DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, DestroyFailedException, UnsupportedVaultException {
-		final String pw = "asd";
-
-		final ByteArrayOutputStream out = new ByteArrayOutputStream();
-		cryptor.encryptMasterKey(out, pw);
-		cryptor.destroy();
-		IOUtils.closeQuietly(out);
-
-		// all these passwords are expected to fail.
-		final String[] wrongPws = {"a", "as", "asdf", "sdf", "das", "dsa", "foo", "bar", "baz"};
-		final Cryptor decryptor = DaggerCryptoTestComponent.create().cryptor();
-		for (final String wrongPw : wrongPws) {
-			final InputStream in = new ByteArrayInputStream(out.toByteArray());
-			try {
-				decryptor.decryptMasterKey(in, wrongPw);
-				Assert.fail("should not succeed.");
-			} catch (WrongPasswordException e) {
-				continue;
-			} finally {
-				IOUtils.closeQuietly(in);
-			}
-		}
-	}
-
-	@Test(expected = DecryptFailedException.class, timeout = 10000)
-	public void testIntegrityViolationDuringDecryption() throws IOException, DecryptFailedException, EncryptFailedException {
-		// our test plaintext data:
-		final byte[] plaintextData = "Hello World".getBytes();
-		final InputStream plaintextIn = new ByteArrayInputStream(plaintextData);
-
-		// encrypt:
-		final ByteBuffer encryptedData = ByteBuffer.allocate(104 + plaintextData.length + 4096 + 32); // header + content + maximum possible size obfuscation padding + 32 bytes mac (per each 32k)
-		final SeekableByteChannel encryptedOut = new ByteBufferBackedSeekableChannel(encryptedData);
-		cryptor.encryptFile(plaintextIn, encryptedOut);
-		IOUtils.closeQuietly(plaintextIn);
-		IOUtils.closeQuietly(encryptedOut);
-
-		encryptedData.position(0);
-
-		// toggle one bit inf first content byte:
-		encryptedData.position(64);
-		final byte fifthByte = encryptedData.get();
-		encryptedData.position(64);
-		encryptedData.put((byte) (fifthByte ^ 0x01));
-
-		encryptedData.position(0);
-
-		// decrypt modified content (should fail with DecryptFailedException):
-		final SeekableByteChannel encryptedIn = new ByteBufferBackedSeekableChannel(encryptedData);
-		final ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
-		cryptor.decryptFile(encryptedIn, plaintextOut, true);
-	}
-
-	@Test(timeout = 10000)
-	public void testEncryptionAndDecryption() throws IOException, DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, EncryptFailedException {
-		// our test plaintext data:
-		final byte[] plaintextData = "Hello World".getBytes();
-		final InputStream plaintextIn = new ByteArrayInputStream(plaintextData);
-
-		// encrypt:
-		final ByteBuffer encryptedData = ByteBuffer.allocate(104 + plaintextData.length + 4096 + 32); // header + content + maximum possible size obfuscation padding + 32 bytes mac (per each 32k)
-		final SeekableByteChannel encryptedOut = new ByteBufferBackedSeekableChannel(encryptedData);
-		cryptor.encryptFile(plaintextIn, encryptedOut);
-		IOUtils.closeQuietly(plaintextIn);
-		IOUtils.closeQuietly(encryptedOut);
-
-		encryptedData.position(0);
-
-		// decrypt file size:
-		final SeekableByteChannel encryptedIn = new ByteBufferBackedSeekableChannel(encryptedData);
-		final Long filesize = cryptor.decryptedContentLength(encryptedIn);
-		Assert.assertEquals(plaintextData.length, filesize.longValue());
-
-		// decrypt:
-		final ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
-		final Long numDecryptedBytes = cryptor.decryptFile(encryptedIn, plaintextOut, true);
-		IOUtils.closeQuietly(encryptedIn);
-		IOUtils.closeQuietly(plaintextOut);
-		Assert.assertEquals(filesize.longValue(), numDecryptedBytes.longValue());
-
-		// check decrypted data:
-		final byte[] result = plaintextOut.toByteArray();
-		Assert.assertArrayEquals(plaintextData, result);
-	}
-
-	@Test(timeout = 10000)
-	public void testPartialDecryption() throws IOException, DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, EncryptFailedException {
-		// 8MiB test plaintext data:
-		final byte[] plaintextData = new byte[2097152 * Integer.BYTES];
-		final ByteBuffer bbIn = ByteBuffer.wrap(plaintextData);
-		for (int i = 0; i < 2097152; i++) {
-			bbIn.putInt(i);
-		}
-		final InputStream plaintextIn = new ByteArrayInputStream(plaintextData);
-
-		// encrypt:
-		final ByteBuffer encryptedData = ByteBuffer.allocate((int) (104 + plaintextData.length * 1.2));
-		final SeekableByteChannel encryptedOut = new ByteBufferBackedSeekableChannel(encryptedData);
-		cryptor.encryptFile(plaintextIn, encryptedOut);
-		IOUtils.closeQuietly(plaintextIn);
-		IOUtils.closeQuietly(encryptedOut);
-
-		encryptedData.position(0);
-
-		// decrypt:
-		final SeekableByteChannel encryptedIn = new ByteBufferBackedSeekableChannel(encryptedData);
-		final ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream();
-		final Long numDecryptedBytes = cryptor.decryptRange(encryptedIn, plaintextOut, 260000 * Integer.BYTES, 4000 * Integer.BYTES, true);
-		IOUtils.closeQuietly(encryptedIn);
-		IOUtils.closeQuietly(plaintextOut);
-		Assert.assertTrue(numDecryptedBytes > 0);
-
-		// check decrypted data:
-		final byte[] result = plaintextOut.toByteArray();
-		final byte[] expected = Arrays.copyOfRange(plaintextData, 260000 * Integer.BYTES, 264000 * Integer.BYTES);
-		Assert.assertArrayEquals(expected, result);
-	}
-
-	@Test(timeout = 10000)
-	public void testEncryptionOfFilenames() throws IOException, DecryptFailedException {
-
-		// directory paths
-		final String originalPath1 = "foo/bar/baz";
-		final String encryptedPath1a = cryptor.encryptDirectoryPath(originalPath1, "/");
-		final String encryptedPath1b = cryptor.encryptDirectoryPath(originalPath1, "/");
-		Assert.assertEquals(encryptedPath1a, encryptedPath1b);
-
-		// long file names
-		final String str50chars = "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee";
-		final String originalPath2 = str50chars + str50chars + str50chars + str50chars + str50chars + "_isLongerThan255Chars.txt";
-		final String encryptedPath2a = cryptor.encryptFilename(originalPath2);
-		final String encryptedPath2b = cryptor.encryptFilename(originalPath2);
-		Assert.assertEquals(encryptedPath2a, encryptedPath2b);
-		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);
-		final String encryptedPath3b = cryptor.encryptFilename(originalPath3);
-		Assert.assertEquals(encryptedPath3a, encryptedPath3b);
-		final String decryptedPath3 = cryptor.decryptFilename(encryptedPath3a);
-		Assert.assertEquals(originalPath3, decryptedPath3);
-	}
-
-}

+ 0 - 79
main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/ByteBufferBackedSeekableChannel.java

@@ -1,79 +0,0 @@
-package org.cryptomator.crypto.aes256;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.SeekableByteChannel;
-
-class ByteBufferBackedSeekableChannel implements SeekableByteChannel {
-
-	private final ByteBuffer buffer;
-	private boolean open = true;
-
-	ByteBufferBackedSeekableChannel(ByteBuffer buffer) {
-		this.buffer = buffer;
-	}
-
-	@Override
-	public boolean isOpen() {
-		return open;
-	}
-
-	@Override
-	public void close() throws IOException {
-		open = false;
-	}
-
-	@Override
-	public int read(ByteBuffer dst) throws IOException {
-		if (buffer.remaining() == 0) {
-			return -1;
-		}
-		int num = Math.min(dst.remaining(), buffer.remaining());
-		byte[] bytes = new byte[num];
-		buffer.get(bytes);
-		dst.put(bytes);
-		return num;
-	}
-
-	@Override
-	public int write(ByteBuffer src) throws IOException {
-		int num = src.remaining();
-		if (buffer.remaining() < src.remaining()) {
-			buffer.limit(buffer.limit() + src.remaining());
-		}
-		buffer.put(src);
-		return num;
-	}
-
-	@Override
-	public long position() throws IOException {
-		return buffer.position();
-	}
-
-	@Override
-	public SeekableByteChannel position(long newPosition) throws IOException {
-		if (newPosition > Integer.MAX_VALUE) {
-			throw new UnsupportedOperationException();
-		}
-		if (newPosition > buffer.limit()) {
-			buffer.limit((int) newPosition);
-		}
-		buffer.position((int) newPosition);
-		return this;
-	}
-
-	@Override
-	public long size() throws IOException {
-		return buffer.limit();
-	}
-
-	@Override
-	public SeekableByteChannel truncate(long size) throws IOException {
-		if (size > Integer.MAX_VALUE) {
-			throw new UnsupportedOperationException();
-		}
-		buffer.limit((int) size);
-		return this;
-	}
-
-}

+ 0 - 15
main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/CryptoTestComponent.java

@@ -1,15 +0,0 @@
-package org.cryptomator.crypto.aes256;
-
-import javax.inject.Singleton;
-
-import org.cryptomator.crypto.Cryptor;
-
-import dagger.Component;
-
-@Singleton
-@Component(modules = CryptoTestModule.class)
-interface CryptoTestComponent {
-
-	Cryptor cryptor();
-
-}

+ 0 - 25
main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/CryptoTestModule.java

@@ -1,25 +0,0 @@
-package org.cryptomator.crypto.aes256;
-
-import java.security.SecureRandom;
-
-import org.cryptomator.crypto.Cryptor;
-
-import dagger.Module;
-import dagger.Provides;
-
-@Module
-class CryptoTestModule {
-
-	@Provides
-	@SuppressWarnings("deprecation")
-	SecureRandom provideRandomNumberGenerator() {
-		// we use this class for testing only, as unit tests on CI servers tend to stall, if they rely on true randomness.
-		return new InsecureRandomMock();
-	}
-
-	@Provides
-	Cryptor provideCryptor(SecureRandom secureRandom) {
-		return new Aes256Cryptor(secureRandom);
-	}
-
-}

+ 0 - 23
main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/InsecureRandomMock.java

@@ -1,23 +0,0 @@
-package org.cryptomator.crypto.aes256;
-
-import java.security.SecureRandom;
-import java.util.Random;
-
-/**
- * <b>DO NOT USE</b>
- * 
- * This class is for testing only.
- */
-@Deprecated // marked as deprecated and made package-private inside /src/test/java to avoid accidential use.
-class InsecureRandomMock extends SecureRandom {
-
-	private static final long serialVersionUID = 1505563778398085504L;
-	private final Random random = new Random();
-
-	@Override
-	public void nextBytes(byte[] bytes) {
-		// let the deterministic RNG do the work:
-		this.random.nextBytes(bytes);
-	}
-
-}

+ 0 - 33
main/crypto-aes/src/test/resources/log4j2.xml

@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!--
-  Copyright (c) 2014 Markus Kreusch
-  This file is licensed under the terms of the MIT license.
-  See the LICENSE.txt file for more info.
-  
-  Contributors:
-      Sebastian Stenzel - log4j config for WebDAV unit tests
--->
-<Configuration status="WARN">
-
-	<Appenders>
-		<Console name="Console" target="SYSTEM_OUT">
-			<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
-			<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="ACCEPT" />
-		</Console>
-		<Console name="StdErr" target="SYSTEM_ERR">
-			<PatternLayout pattern="%16d %-5p [%c{1}:%L] %m%n" />
-			<ThresholdFilter level="WARN" onMatch="ACCEPT" onMismatch="DENY" />
-		</Console>
-	</Appenders>
-
-	<Loggers>
-		<!-- show our own debug messages: -->
-		<Logger name="org.cryptomator" level="DEBUG" />
-		<!-- mute dependencies: -->
-		<Root level="INFO">
-			<AppenderRef ref="Console" />
-			<AppenderRef ref="StdErr" />
-		</Root>
-	</Loggers>
-
-</Configuration>

+ 0 - 1
main/crypto-api/.gitignore

@@ -1 +0,0 @@
-/target/

+ 0 - 50
main/crypto-api/pom.xml

@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-  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
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<parent>
-		<groupId>org.cryptomator</groupId>
-		<artifactId>main</artifactId>
-		<version>0.11.0-SNAPSHOT</version>
-	</parent>
-	<artifactId>crypto-api</artifactId>
-	<name>Cryptomator cryptographic module API</name>
-
-	<dependencies>
-		<!-- commons -->
-		<dependency>
-			<groupId>commons-io</groupId>
-			<artifactId>commons-io</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.apache.commons</groupId>
-			<artifactId>commons-lang3</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.apache.commons</groupId>
-			<artifactId>commons-collections4</artifactId>
-		</dependency>
-		
-		<!-- Tests -->
-		<dependency>
-			<groupId>org.cryptomator</groupId>
-			<artifactId>commons-test</artifactId>
-		</dependency>
-	</dependencies>
-	
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.jacoco</groupId>
-				<artifactId>jacoco-maven-plugin</artifactId>
-			</plugin>
-		</plugins>
-	</build>
-</project>

+ 0 - 85
main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptorDecorator.java

@@ -1,85 +0,0 @@
-package org.cryptomator.crypto;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.channels.SeekableByteChannel;
-
-import javax.security.auth.DestroyFailedException;
-
-import org.cryptomator.crypto.exceptions.DecryptFailedException;
-import org.cryptomator.crypto.exceptions.EncryptFailedException;
-import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
-import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
-import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
-import org.cryptomator.crypto.exceptions.WrongPasswordException;
-
-public class AbstractCryptorDecorator implements Cryptor {
-
-	protected final Cryptor cryptor;
-
-	public AbstractCryptorDecorator(Cryptor cryptor) {
-		this.cryptor = cryptor;
-	}
-
-	@Override
-	public void randomizeMasterKey() {
-		cryptor.randomizeMasterKey();
-	}
-
-	@Override
-	public void encryptMasterKey(OutputStream out, CharSequence password) throws IOException {
-		cryptor.encryptMasterKey(out, password);
-	}
-
-	@Override
-	public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException, UnsupportedVaultException {
-		cryptor.decryptMasterKey(in, password);
-	}
-
-	@Override
-	public String encryptDirectoryPath(String cleartextDirectoryId, String nativePathSep) {
-		return cryptor.encryptDirectoryPath(cleartextDirectoryId, nativePathSep);
-	}
-
-	@Override
-	public String encryptFilename(String cleartextName) {
-		return cryptor.encryptFilename(cleartextName);
-	}
-
-	@Override
-	public String decryptFilename(String ciphertextName) throws DecryptFailedException {
-		return cryptor.decryptFilename(ciphertextName);
-	}
-
-	@Override
-	public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException, MacAuthenticationFailedException {
-		return cryptor.decryptedContentLength(encryptedFile);
-	}
-
-	@Override
-	public Long decryptFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile, boolean authenticate) throws IOException, DecryptFailedException {
-		return cryptor.decryptFile(encryptedFile, plaintextFile, authenticate);
-	}
-
-	@Override
-	public Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length, boolean authenticate) throws IOException, DecryptFailedException {
-		return cryptor.decryptRange(encryptedFile, plaintextFile, pos, length, authenticate);
-	}
-
-	@Override
-	public Long encryptFile(InputStream plaintextFile, SeekableByteChannel encryptedFile) throws IOException, EncryptFailedException {
-		return cryptor.encryptFile(plaintextFile, encryptedFile);
-	}
-
-	@Override
-	public void destroy() throws DestroyFailedException {
-		cryptor.destroy();
-	}
-
-	@Override
-	public boolean isDestroyed() {
-		return cryptor.isDestroyed();
-	}
-
-}

+ 0 - 102
main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java

@@ -1,102 +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;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.channels.SeekableByteChannel;
-
-import javax.security.auth.Destroyable;
-
-import org.cryptomator.crypto.exceptions.DecryptFailedException;
-import org.cryptomator.crypto.exceptions.EncryptFailedException;
-import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
-import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
-import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
-import org.cryptomator.crypto.exceptions.WrongPasswordException;
-
-/**
- * Provides access to cryptographic functions. All methods are threadsafe.
- */
-public interface Cryptor extends Destroyable {
-
-	/**
-	 * Assigns new random bytes to the keys in this Cryptor instance.
-	 */
-	void randomizeMasterKey();
-
-	/**
-	 * Encrypts the current masterKey with the given password and writes the result to the given output stream.
-	 */
-	void encryptMasterKey(OutputStream out, CharSequence password) throws IOException;
-
-	/**
-	 * Reads the encrypted masterkey from the given input stream and decrypts it with the given password.
-	 * 
-	 * @throws DecryptFailedException If the decryption failed for various reasons (including wrong password).
-	 * @throws WrongPasswordException If the provided password was wrong. Note: Sometimes the algorithm itself fails due to a wrong password. In this case a DecryptFailedException will be thrown.
-	 * @throws UnsupportedKeyLengthException If the masterkey has been encrypted with a higher key length than supported by the system. In this case Java JCE needs to be installed.
-	 * @throws UnsupportedVaultException If the masterkey file is too old or too modern.
-	 */
-	void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException, UnsupportedVaultException;
-
-	/**
-	 * Encrypts a given plaintext path representing a directory structure. See {@link #encryptFilename(String, CryptorMetadataSupport)} for contents inside directories.
-	 * 
-	 * @param cleartextDirectoryId A unique directory id
-	 * @param nativePathSep Path separator like "/" used on local file system. Must not be null, even if cleartextPath is a sole file name without any path separators.
-	 * @return Encrypted path.
-	 */
-	String encryptDirectoryPath(String cleartextDirectoryId, String nativePathSep);
-
-	/**
-	 * Encrypts the name of a file. See {@link #encryptDirectoryPath(String, char)} for parent dir.
-	 * 
-	 * @param cleartextName A plaintext filename without any preceeding directory paths.
-	 * @return Encrypted filename.
-	 */
-	String encryptFilename(String cleartextName);
-
-	/**
-	 * Decrypts the name of a file.
-	 * 
-	 * @param ciphertextName A ciphertext filename without any preceeding directory paths.
-	 * @return Decrypted filename.
-	 * @throws DecryptFailedException If the decryption failed for various reasons (including wrong password).
-	 */
-	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.
-	 * @return Content length of the decrypted file or <code>null</code> if unknown.
-	 * @throws MacAuthenticationFailedException If the MAC auth failed.
-	 */
-	Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException, MacAuthenticationFailedException;
-
-	/**
-	 * @return Number of decrypted bytes. This might not be equal to the encrypted file size due to optional metadata written to it.
-	 * @throws DecryptFailedException If decryption failed
-	 */
-	Long decryptFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile, boolean authenticate) throws IOException, DecryptFailedException;
-
-	/**
-	 * @param pos First byte (inclusive)
-	 * @param length Number of requested bytes beginning at pos.
-	 * @return Number of decrypted bytes. This might not be equal to the number of bytes requested due to potential overheads.
-	 * @throws DecryptFailedException If decryption failed
-	 */
-	Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length, boolean authenticate) throws IOException, DecryptFailedException;
-
-	/**
-	 * @return Number of encrypted bytes. This might not be equal to the encrypted file size due to optional metadata written to it.
-	 */
-	Long encryptFile(InputStream plaintextFile, SeekableByteChannel encryptedFile) throws IOException, EncryptFailedException;
-
-}

+ 0 - 26
main/crypto-api/src/main/java/org/cryptomator/crypto/CryptorIOSampling.java

@@ -1,26 +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;
-
-/**
- * Optional monitoring interface. If a cryptor implements this interface, it counts bytes de- and encrypted in a thread-safe manner.
- */
-public interface CryptorIOSampling {
-
-	/**
-	 * @return Number of encrypted bytes since the last reset.
-	 */
-	long pollEncryptedBytes(boolean resetCounter);
-
-	/**
-	 * @return Number of decrypted bytes since the last reset.
-	 */
-	long pollDecryptedBytes(boolean resetCounter);
-
-}

+ 0 - 65
main/crypto-api/src/main/java/org/cryptomator/crypto/PathCachingCryptorDecorator.java

@@ -1,65 +0,0 @@
-package org.cryptomator.crypto;
-
-import java.util.Map;
-
-import org.apache.commons.collections4.BidiMap;
-import org.apache.commons.collections4.bidimap.AbstractDualBidiMap;
-import org.apache.commons.collections4.map.LRUMap;
-import org.cryptomator.crypto.exceptions.DecryptFailedException;
-
-public class PathCachingCryptorDecorator extends AbstractCryptorDecorator {
-
-	private static final int MAX_CACHED_PATHS = 5000;
-	private static final int MAX_CACHED_NAMES = 5000;
-
-	private final Map<String, String> pathCache = new LRUMap<>(MAX_CACHED_PATHS); // <cleartextDirectoryId, ciphertextPath>
-	private final BidiMap<String, String> nameCache = new BidiLRUMap<>(MAX_CACHED_NAMES); // <cleartextName, ciphertextName>
-
-	private PathCachingCryptorDecorator(Cryptor cryptor) {
-		super(cryptor);
-	}
-
-	public static Cryptor decorate(Cryptor cryptor) {
-		return new PathCachingCryptorDecorator(cryptor);
-	}
-
-	/* Cryptor */
-
-	@Override
-	public String encryptDirectoryPath(String cleartextDirectoryId, String nativePathSep) {
-		return pathCache.computeIfAbsent(cleartextDirectoryId, id -> cryptor.encryptDirectoryPath(id, nativePathSep));
-	}
-
-	@Override
-	public String encryptFilename(String cleartextName) {
-		return nameCache.computeIfAbsent(cleartextName, name -> cryptor.encryptFilename(name));
-	}
-
-	@Override
-	public String decryptFilename(String ciphertextName) throws DecryptFailedException {
-		String cleartextName = nameCache.getKey(ciphertextName);
-		if (cleartextName == null) {
-			cleartextName = cryptor.decryptFilename(ciphertextName);
-			nameCache.put(cleartextName, ciphertextName);
-		}
-		return cleartextName;
-	}
-
-	private static class BidiLRUMap<K, V> extends AbstractDualBidiMap<K, V> {
-
-		BidiLRUMap(int maxSize) {
-			super(new LRUMap<K, V>(maxSize), new LRUMap<V, K>(maxSize));
-		}
-
-		protected BidiLRUMap(final Map<K, V> normalMap, final Map<V, K> reverseMap, final BidiMap<V, K> inverseBidiMap) {
-			super(normalMap, reverseMap, inverseBidiMap);
-		}
-
-		@Override
-		protected BidiMap<V, K> createBidiMap(Map<V, K> normalMap, Map<K, V> reverseMap, BidiMap<K, V> inverseMap) {
-			return new BidiLRUMap<V, K>(normalMap, reverseMap, inverseMap);
-		}
-
-	}
-
-}

+ 0 - 118
main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingCryptorDecorator.java

@@ -1,118 +0,0 @@
-package org.cryptomator.crypto;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.channels.SeekableByteChannel;
-import java.util.concurrent.atomic.LongAdder;
-
-import org.cryptomator.crypto.exceptions.DecryptFailedException;
-import org.cryptomator.crypto.exceptions.EncryptFailedException;
-
-/**
- * Decorates the Cryptor by decorating the In- and OutputStreams used during de-/encryption.
- */
-public class SamplingCryptorDecorator extends AbstractCryptorDecorator implements CryptorIOSampling {
-
-	private final LongAdder encryptedBytes;
-	private final LongAdder decryptedBytes;
-
-	private SamplingCryptorDecorator(Cryptor cryptor) {
-		super(cryptor);
-		encryptedBytes = new LongAdder();
-		decryptedBytes = new LongAdder();
-	}
-
-	public static Cryptor decorate(Cryptor cryptor) {
-		return new SamplingCryptorDecorator(cryptor);
-	}
-
-	@Override
-	public long pollEncryptedBytes(boolean resetCounter) {
-		if (resetCounter) {
-			return encryptedBytes.sumThenReset();
-		} else {
-			return encryptedBytes.sum();
-		}
-	}
-
-	@Override
-	public long pollDecryptedBytes(boolean resetCounter) {
-		if (resetCounter) {
-			return decryptedBytes.sumThenReset();
-		} else {
-			return decryptedBytes.sum();
-		}
-	}
-
-	/* Cryptor */
-
-	@Override
-	public Long decryptFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile, boolean authenticate) throws IOException, DecryptFailedException {
-		final OutputStream countingOutputStream = new CountingOutputStream(decryptedBytes, plaintextFile);
-		return cryptor.decryptFile(encryptedFile, countingOutputStream, authenticate);
-	}
-
-	@Override
-	public Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length, boolean authenticate) throws IOException, DecryptFailedException {
-		final OutputStream countingOutputStream = new CountingOutputStream(decryptedBytes, plaintextFile);
-		return cryptor.decryptRange(encryptedFile, countingOutputStream, pos, length, authenticate);
-	}
-
-	@Override
-	public Long encryptFile(InputStream plaintextFile, SeekableByteChannel encryptedFile) throws IOException, EncryptFailedException {
-		final InputStream countingInputStream = new CountingInputStream(encryptedBytes, plaintextFile);
-		return cryptor.encryptFile(countingInputStream, encryptedFile);
-	}
-
-	private class CountingInputStream extends InputStream {
-
-		private final InputStream in;
-		private final LongAdder counter;
-
-		private CountingInputStream(LongAdder counter, InputStream in) {
-			this.in = in;
-			this.counter = counter;
-		}
-
-		@Override
-		public int read() throws IOException {
-			int count = in.read();
-			counter.add(count);
-			return count;
-		}
-
-		@Override
-		public int read(byte[] b, int off, int len) throws IOException {
-			int count = in.read(b, off, len);
-			counter.add(count);
-			return count;
-		}
-
-	}
-
-	private class CountingOutputStream extends OutputStream {
-
-		private final OutputStream out;
-		private final LongAdder counter;
-
-		private CountingOutputStream(LongAdder counter, OutputStream out) {
-			this.out = out;
-			this.counter = counter;
-		}
-
-		@Override
-		public void write(int b) throws IOException {
-			counter.increment();
-			out.write(b);
-		}
-
-		@Override
-		public void write(byte[] b, int off, int len) throws IOException {
-			counter.add(len);
-			out.write(b, off, len);
-		}
-
-	}
-
-}

+ 0 - 15
main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/CryptingException.java

@@ -1,15 +0,0 @@
-package org.cryptomator.crypto.exceptions;
-
-import java.io.IOException;
-
-public class CryptingException extends IOException {
-	private static final long serialVersionUID = -6622699014483319376L;
-
-	public CryptingException(String string) {
-		super(string);
-	}
-
-	public CryptingException(String string, Throwable t) {
-		super(string, t);
-	}
-}

+ 0 - 13
main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java

@@ -1,13 +0,0 @@
-package org.cryptomator.crypto.exceptions;
-
-public class DecryptFailedException extends CryptingException {
-	private static final long serialVersionUID = -3855673600374897828L;
-
-	public DecryptFailedException(Throwable t) {
-		super("Decryption failed.", t);
-	}
-
-	public DecryptFailedException(String msg) {
-		super(msg);
-	}
-}

+ 0 - 13
main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/EncryptFailedException.java

@@ -1,13 +0,0 @@
-package org.cryptomator.crypto.exceptions;
-
-public class EncryptFailedException extends CryptingException {
-	private static final long serialVersionUID = -3855673600374897828L;
-
-	public EncryptFailedException(Throwable t) {
-		super("Encryption failed.", t);
-	}
-
-	public EncryptFailedException(String msg) {
-		super(msg);
-	}
-}

+ 0 - 11
main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/MacAuthenticationFailedException.java

@@ -1,11 +0,0 @@
-package org.cryptomator.crypto.exceptions;
-
-public class MacAuthenticationFailedException extends DecryptFailedException {
-
-	private static final long serialVersionUID = -5577052361643658772L;
-
-	public MacAuthenticationFailedException(String msg) {
-		super(msg);
-	}
-
-}

+ 0 - 11
main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/MasterkeyDecryptionException.java

@@ -1,11 +0,0 @@
-package org.cryptomator.crypto.exceptions;
-
-public class MasterkeyDecryptionException extends Exception {
-
-	private static final long serialVersionUID = -6241452734672333206L;
-
-	public MasterkeyDecryptionException(String string) {
-		super(string);
-	}
-
-}

+ 0 - 23
main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedKeyLengthException.java

@@ -1,23 +0,0 @@
-package org.cryptomator.crypto.exceptions;
-
-public class UnsupportedKeyLengthException extends MasterkeyDecryptionException {
-	private static final long serialVersionUID = 8114147446419390179L;
-
-	private final int requestedLength;
-	private final int supportedLength;
-
-	public UnsupportedKeyLengthException(int length, int maxLength) {
-		super(String.format("Key length (%d) exceeds policy maximum (%d).", length, maxLength));
-		this.requestedLength = length;
-		this.supportedLength = maxLength;
-	}
-
-	public int getRequestedLength() {
-		return requestedLength;
-	}
-
-	public int getSupportedLength() {
-		return supportedLength;
-	}
-
-}

+ 0 - 32
main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedVaultException.java

@@ -1,32 +0,0 @@
-package org.cryptomator.crypto.exceptions;
-
-public class UnsupportedVaultException extends Exception {
-
-	private static final long serialVersionUID = -5147549533387945622L;
-
-	private final Integer detectedVersion;
-	private final Integer supportedVersion;
-
-	public UnsupportedVaultException(Integer detectedVersion, Integer supportedVersion) {
-		super("Tried to open vault of version " + detectedVersion + ", but can only handle version " + supportedVersion);
-		this.detectedVersion = detectedVersion;
-		this.supportedVersion = supportedVersion;
-	}
-
-	public Integer getDetectedVersion() {
-		return detectedVersion;
-	}
-
-	public Integer getSupportedVersion() {
-		return supportedVersion;
-	}
-
-	public boolean isVaultOlderThanSoftware() {
-		return detectedVersion == null || detectedVersion < supportedVersion;
-	}
-
-	public boolean isSoftwareOlderThanVault() {
-		return detectedVersion > supportedVersion;
-	}
-
-}

+ 0 - 9
main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/WrongPasswordException.java

@@ -1,9 +0,0 @@
-package org.cryptomator.crypto.exceptions;
-
-public class WrongPasswordException extends MasterkeyDecryptionException {
-	private static final long serialVersionUID = -602047799678568780L;
-
-	public WrongPasswordException() {
-		super("Wrong password.");
-	}
-}