diff --git a/main/core/.gitignore b/main/core/.gitignore deleted file mode 100644 index b83d22266..000000000 --- a/main/core/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target/ diff --git a/main/core/pom.xml b/main/core/pom.xml deleted file mode 100644 index 2450eacab..000000000 --- a/main/core/pom.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - 4.0.0 - - org.cryptomator - main - 0.11.0-SNAPSHOT - - core - Cryptomator WebDAV and I/O module - - - 9.3.3.v20150827 - 2.11.0 - - - - - org.cryptomator - crypto-api - - - - - org.eclipse.jetty - jetty-server - ${jetty.version} - - - org.eclipse.jetty - jetty-webapp - ${jetty.version} - - - commons-httpclient - commons-httpclient - test - - - - - org.apache.jackrabbit - jackrabbit-webdav - ${jackrabbit.version} - - - - - com.google.guava - guava - - - - - commons-io - commons-io - - - org.apache.commons - commons-lang3 - - - - - com.fasterxml.jackson.core - jackson-databind - - - - - org.cryptomator - commons-test - - - - - - - org.jacoco - jacoco-maven-plugin - - - - diff --git a/main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java b/main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java deleted file mode 100644 index ef1b29b65..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java +++ /dev/null @@ -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 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 failingMacCollection, final Collection 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 failingMacCollection, final Collection 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(); - } - - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/WindowsSucksServlet.java b/main/core/src/main/java/org/cryptomator/webdav/WindowsSucksServlet.java deleted file mode 100644 index 06d9e2497..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/WindowsSucksServlet.java +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 Sebastian Stenzel - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - * - * Contributors: - * Sebastian Stenzel - initial API and implementation - ******************************************************************************/ -package org.cryptomator.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); - } - -} \ No newline at end of file diff --git a/main/core/src/main/java/org/cryptomator/webdav/exceptions/DavRuntimeException.java b/main/core/src/main/java/org/cryptomator/webdav/exceptions/DavRuntimeException.java deleted file mode 100644 index 9b8893a7b..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/exceptions/DavRuntimeException.java +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2014 Sebastian Stenzel - * This file is licensed under the terms of the MIT license. - * See the LICENSE.txt file for more info. - * - * Contributors: - * Sebastian Stenzel - initial API and implementation - ******************************************************************************/ -package org.cryptomator.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(); - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/exceptions/DecryptFailedRuntimeException.java b/main/core/src/main/java/org/cryptomator/webdav/exceptions/DecryptFailedRuntimeException.java deleted file mode 100644 index 12e28da11..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/exceptions/DecryptFailedRuntimeException.java +++ /dev/null @@ -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(); - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java deleted file mode 100644 index 613b96129..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/AbstractEncryptedNode.java +++ /dev/null @@ -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(DavPropertyName.CREATIONDATE, FileTimeUtils.toRfc1123String(attrs.creationTime()))); - properties.add(new DefaultDavProperty(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 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; - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CleartextLocatorFactory.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CleartextLocatorFactory.java deleted file mode 100644 index d4c696109..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CleartextLocatorFactory.java +++ /dev/null @@ -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; - } - } - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoResourceFactory.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoResourceFactory.java deleted file mode 100644 index 20572ad5f..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoResourceFactory.java +++ /dev/null @@ -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 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 true 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 true 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.
- * - * bytes=100-200
- * bytes=-500
- * bytes=1000- - *
- * - * @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 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 false, 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 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; - - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoWarningHandler.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoWarningHandler.java deleted file mode 100644 index 104dd65b4..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/CryptoWarningHandler.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.cryptomator.webdav.jackrabbit; - -import java.util.Collection; - -class CryptoWarningHandler { - - private final Collection resourcesWithInvalidMac; - private final Collection whitelistedResources; - - public CryptoWarningHandler(Collection resourcesWithInvalidMac, Collection 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); - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionImpl.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionImpl.java deleted file mode 100644 index 01d2cf92d..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionImpl.java +++ /dev/null @@ -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 lockTokens = new HashSet(); - private final HashSet references = new HashSet(); - - @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); - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionProviderImpl.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionProviderImpl.java deleted file mode 100644 index 54cd05d6a..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/DavSessionProviderImpl.java +++ /dev/null @@ -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); - } - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java deleted file mode 100644 index 75d8e4752..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedDir.java +++ /dev/null @@ -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(DavPropertyName.ISCOLLECTION, 1)); - } - - /** - * @return Path or null, 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 null, 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 directoryStream = Files.newDirectoryStream(dirPath, DIRECTORY_CONTENT_FILTER)) { - final List 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 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 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 { - - private final Queue directories; - - private RecursiveDirectoryDeletingVisitor(Queue 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 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; - } - - } - - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java deleted file mode 100644 index 740b7926d..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFile.java +++ /dev/null @@ -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(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(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); - } - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFilePart.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFilePart.java deleted file mode 100644 index e9af363b7..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/EncryptedFilePart.java +++ /dev/null @@ -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 range; - - public EncryptedFilePart(CryptoResourceFactory factory, DavResourceLocator locator, DavSession session, Pair 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(contentLength - upper, contentLength - 1); - } else if (upper == null) { - range = new ImmutablePair(lower, contentLength - 1); - } else { - range = new ImmutablePair(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); - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileConstants.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileConstants.java deleted file mode 100644 index 5e53382e5..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileConstants.java +++ /dev/null @@ -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.
- * Parent folder path uses up to 58 chars (sha256 -> 32 bytes base32 encoded to 56 bytes + two slashes). That in mind we don't want the total path to be longer than 255 chars.
- * 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 DIRECTORY_CONTENT_FILTER = new Filter() { - @Override - public boolean accept(Path entry) throws IOException { - return ENCRYPTED_FILE_MATCHER.matches(entry); - } - }; - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileTimeUtils.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileTimeUtils.java deleted file mode 100644 index 10588325b..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FileTimeUtils.java +++ /dev/null @@ -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); - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java deleted file mode 100644 index 95f46cf8d..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/FilenameTranslator.java +++ /dev/null @@ -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, String> directoryIdCache = new LRUMap<>(MAX_CACHED_DIRECTORY_IDS); // - private final Map, LongFilenameMetadata> metadataCache = new LRUMap<>(MAX_CACHED_METADATA_FILES); // - - 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 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 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 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.
- * This means that we need a workaround for filenames longer than the limit defined in {@link FileConstants#ENCRYPTED_FILENAME_LENGTH_LIMIT}.
- *
- * For filenames longer than this limit we use a metadata file containing the full encrypted paths. For the actual filename a unique alternative is created by concatenating the metadata filename - * and a unique id. - */ - private String getEncryptedFilename(String cleartextFilename, String basicExt, String longExt) throws IOException { - final String ivAndCiphertext = cryptor.encryptFilename(cleartextFilename); - if (ivAndCiphertext.length() + basicExt.length() > ENCRYPTED_FILENAME_LENGTH_LIMIT) { - final String metadataGroup = ivAndCiphertext.substring(0, LONG_NAME_PREFIX_LENGTH); - final LongFilenameMetadata metadata = readMetadata(metadataGroup); - final String longFilename = metadataGroup + metadata.getOrCreateUuidForEncryptedFilename(ivAndCiphertext).toString() + longExt; - this.writeMetadata(metadataGroup, metadata); - return longFilename; - } else { - return ivAndCiphertext + basicExt; - } - } - - public String getCleartextFilename(String encryptedFilename) throws DecryptFailedException, IOException { - final String ciphertext; - if (StringUtils.endsWithIgnoreCase(encryptedFilename, LONG_FILE_EXT)) { - final String basename = StringUtils.removeEndIgnoreCase(encryptedFilename, LONG_FILE_EXT); - final String metadataGroup = basename.substring(0, LONG_NAME_PREFIX_LENGTH); - final String uuid = basename.substring(LONG_NAME_PREFIX_LENGTH); - final LongFilenameMetadata metadata = readMetadata(metadataGroup); - ciphertext = metadata.getEncryptedFilenameForUUID(UUID.fromString(uuid)); - } else if (StringUtils.endsWithIgnoreCase(encryptedFilename, FILE_EXT)) { - ciphertext = StringUtils.removeEndIgnoreCase(encryptedFilename, FILE_EXT); - } else if (StringUtils.endsWithIgnoreCase(encryptedFilename, LONG_DIR_EXT)) { - final String basename = StringUtils.removeEndIgnoreCase(encryptedFilename, LONG_DIR_EXT); - final String metadataGroup = basename.substring(0, LONG_NAME_PREFIX_LENGTH); - final String uuid = basename.substring(LONG_NAME_PREFIX_LENGTH); - final LongFilenameMetadata metadata = readMetadata(metadataGroup); - ciphertext = metadata.getEncryptedFilenameForUUID(UUID.fromString(uuid)); - } else if (StringUtils.endsWithIgnoreCase(encryptedFilename, DIR_EXT)) { - ciphertext = StringUtils.removeEndIgnoreCase(encryptedFilename, DIR_EXT); - } else { - throw new IllegalArgumentException("Unsupported path component: " + encryptedFilename); - } - return cryptor.decryptFilename(ciphertext); - } - - /* 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 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 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 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 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 getEncryptedFilenames() { - return encryptedFilenames; - } - - // used by jackson - @SuppressWarnings("unused") - public void setEncryptedFilenames(BidiMap encryptedFilenames) { - this.encryptedFilenames = encryptedFilenames; - } - - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/HttpHeaderProperty.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/HttpHeaderProperty.java deleted file mode 100644 index d27bc8be1..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/HttpHeaderProperty.java +++ /dev/null @@ -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 { - - private final String value; - - public HttpHeaderProperty(String key, String value) { - super(DavPropertyName.create(key), true); - this.value = value; - } - - @Override - public String getValue() { - return value; - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java deleted file mode 100644 index f34eb69b5..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/NonExistingNode.java +++ /dev/null @@ -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); - } - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/SilentlyFailingFileLock.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/SilentlyFailingFileLock.java deleted file mode 100644 index 8bb30d570..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/SilentlyFailingFileLock.java +++ /dev/null @@ -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(); - } - } - -} diff --git a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java b/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java deleted file mode 100644 index e504190d3..000000000 --- a/main/core/src/main/java/org/cryptomator/webdav/jackrabbit/WebDavServlet.java +++ /dev/null @@ -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 failingMacCollection, final Collection 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"); - } - } - -} diff --git a/main/core/src/test/java/org/cryptomator/webdav/jackrabbit/CryptorMock.java b/main/core/src/test/java/org/cryptomator/webdav/jackrabbit/CryptorMock.java deleted file mode 100644 index aaa1a35e2..000000000 --- a/main/core/src/test/java/org/cryptomator/webdav/jackrabbit/CryptorMock.java +++ /dev/null @@ -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; - } - -} diff --git a/main/core/src/test/java/org/cryptomator/webdav/jackrabbit/RangeRequestTest.java b/main/core/src/test/java/org/cryptomator/webdav/jackrabbit/RangeRequestTest.java deleted file mode 100644 index d54fbdb59..000000000 --- a/main/core/src/test/java/org/cryptomator/webdav/jackrabbit/RangeRequestTest.java +++ /dev/null @@ -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(), new ArrayList(), "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> 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); - } - -} diff --git a/main/core/src/test/resources/log4j2.xml b/main/core/src/test/resources/log4j2.xml deleted file mode 100644 index 39c2f8545..000000000 --- a/main/core/src/test/resources/log4j2.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/main/crypto-aes/pom.xml b/main/crypto-aes/pom.xml deleted file mode 100644 index 36b640ea9..000000000 --- a/main/crypto-aes/pom.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - - 4.0.0 - - org.cryptomator - main - 0.11.0-SNAPSHOT - - crypto-aes - Cryptomator cryptographic module (AES) - Provides stream ciphers and filename pseudonymization functions. - - - 1.51 - - - - - org.cryptomator - crypto-api - - - - - org.bouncycastle - bcprov-jdk15on - ${bouncycastle.version} - - - - - org.cryptomator - siv-mode - 1.0.2 - - - - - commons-io - commons-io - - - org.apache.commons - commons-collections4 - - - org.apache.commons - commons-lang3 - - - commons-codec - commons-codec - - - - - com.fasterxml.jackson.core - jackson-databind - - - - - com.google.dagger - dagger - - - com.google.dagger - dagger-compiler - provided - - - - - org.cryptomator - commons-test - - - - - - - org.jacoco - jacoco-maven-plugin - - - - diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java deleted file mode 100644 index 46810bf32..000000000 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java +++ /dev/null @@ -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. - *

- * 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 - * Apache Commons IOUtils 2.5 - */ - 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(); - } - -} diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java deleted file mode 100644 index 543919c7f..000000000 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java +++ /dev/null @@ -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.
- * - * @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).
- * - * @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(); - -} diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/BlocksData.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/BlocksData.java deleted file mode 100644 index a1a62ba8d..000000000 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/BlocksData.java +++ /dev/null @@ -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; - } - -} diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoComponent.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoComponent.java deleted file mode 100644 index a3fccd27a..000000000 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoComponent.java +++ /dev/null @@ -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(); - -} \ No newline at end of file diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoModule.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoModule.java deleted file mode 100644 index 1bed4cf79..000000000 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoModule.java +++ /dev/null @@ -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); - } - -} diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoWorker.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoWorker.java deleted file mode 100644 index ed56e0b79..000000000 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoWorker.java +++ /dev/null @@ -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 { - - 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 queue; - - public CryptoWorker(Lock lock, Condition blockDone, AtomicLong currentBlock, BlockingQueue 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; - -} diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoWorkerExecutor.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoWorkerExecutor.java deleted file mode 100644 index e63947539..000000000 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/CryptoWorkerExecutor.java +++ /dev/null @@ -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 inputQueue; - private final ExecutorService executorService; - private final Future 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 completionService = new ExecutorCompletionService<>(executorService); - final Collection> 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 true if the work has been added in time. false 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 per worker 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 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 one 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 { - - private final Collection> workers; - private final CompletionService completionService; - - public Supervisor(Collection> 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; - } - } - -} diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/DecryptWorker.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/DecryptWorker.java deleted file mode 100644 index cd2f766f5..000000000 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/DecryptWorker.java +++ /dev/null @@ -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 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; - -} diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/EncryptWorker.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/EncryptWorker.java deleted file mode 100644 index 0a42b4d14..000000000 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/EncryptWorker.java +++ /dev/null @@ -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 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; - -} diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/KeyFile.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/KeyFile.java deleted file mode 100644 index 3bfd40582..000000000 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/KeyFile.java +++ /dev/null @@ -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; - } - -} \ No newline at end of file diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/LengthLimitingOutputStream.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/LengthLimitingOutputStream.java deleted file mode 100644 index 8d52f32b8..000000000 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/LengthLimitingOutputStream.java +++ /dev/null @@ -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(); - } - } - -} diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/LengthObfuscatingInputStream.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/LengthObfuscatingInputStream.java deleted file mode 100644 index 498fa0f29..000000000 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/LengthObfuscatingInputStream.java +++ /dev/null @@ -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; - } - -} diff --git a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java deleted file mode 100644 index ef500daec..000000000 --- a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java +++ /dev/null @@ -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); - } - -} diff --git a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/ByteBufferBackedSeekableChannel.java b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/ByteBufferBackedSeekableChannel.java deleted file mode 100644 index d037327e8..000000000 --- a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/ByteBufferBackedSeekableChannel.java +++ /dev/null @@ -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; - } - -} diff --git a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/CryptoTestComponent.java b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/CryptoTestComponent.java deleted file mode 100644 index 45be2d67c..000000000 --- a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/CryptoTestComponent.java +++ /dev/null @@ -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(); - -} diff --git a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/CryptoTestModule.java b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/CryptoTestModule.java deleted file mode 100644 index c927b179a..000000000 --- a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/CryptoTestModule.java +++ /dev/null @@ -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); - } - -} diff --git a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/InsecureRandomMock.java b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/InsecureRandomMock.java deleted file mode 100644 index 9d54108c5..000000000 --- a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/InsecureRandomMock.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.cryptomator.crypto.aes256; - -import java.security.SecureRandom; -import java.util.Random; - -/** - * DO NOT USE - * - * 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); - } - -} diff --git a/main/crypto-aes/src/test/resources/log4j2.xml b/main/crypto-aes/src/test/resources/log4j2.xml deleted file mode 100644 index 39c2f8545..000000000 --- a/main/crypto-aes/src/test/resources/log4j2.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/main/crypto-api/.gitignore b/main/crypto-api/.gitignore deleted file mode 100644 index b83d22266..000000000 --- a/main/crypto-api/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target/ diff --git a/main/crypto-api/pom.xml b/main/crypto-api/pom.xml deleted file mode 100644 index f39dce6c6..000000000 --- a/main/crypto-api/pom.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - 4.0.0 - - org.cryptomator - main - 0.11.0-SNAPSHOT - - crypto-api - Cryptomator cryptographic module API - - - - - commons-io - commons-io - - - org.apache.commons - commons-lang3 - - - org.apache.commons - commons-collections4 - - - - - org.cryptomator - commons-test - - - - - - - org.jacoco - jacoco-maven-plugin - - - - diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptorDecorator.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptorDecorator.java deleted file mode 100644 index 0db336ec5..000000000 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptorDecorator.java +++ /dev/null @@ -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(); - } - -} diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java deleted file mode 100644 index a17f26238..000000000 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java +++ /dev/null @@ -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 null 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; - -} diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/CryptorIOSampling.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/CryptorIOSampling.java deleted file mode 100644 index c5cd2fe28..000000000 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/CryptorIOSampling.java +++ /dev/null @@ -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); - -} diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/PathCachingCryptorDecorator.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/PathCachingCryptorDecorator.java deleted file mode 100644 index 39241da5f..000000000 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/PathCachingCryptorDecorator.java +++ /dev/null @@ -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 pathCache = new LRUMap<>(MAX_CACHED_PATHS); // - private final BidiMap nameCache = new BidiLRUMap<>(MAX_CACHED_NAMES); // - - 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 extends AbstractDualBidiMap { - - BidiLRUMap(int maxSize) { - super(new LRUMap(maxSize), new LRUMap(maxSize)); - } - - protected BidiLRUMap(final Map normalMap, final Map reverseMap, final BidiMap inverseBidiMap) { - super(normalMap, reverseMap, inverseBidiMap); - } - - @Override - protected BidiMap createBidiMap(Map normalMap, Map reverseMap, BidiMap inverseMap) { - return new BidiLRUMap(normalMap, reverseMap, inverseMap); - } - - } - -} diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingCryptorDecorator.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingCryptorDecorator.java deleted file mode 100644 index e99066efd..000000000 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/SamplingCryptorDecorator.java +++ /dev/null @@ -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); - } - - } - -} diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/CryptingException.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/CryptingException.java deleted file mode 100644 index 279c94028..000000000 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/CryptingException.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java deleted file mode 100644 index d61cb9446..000000000 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/DecryptFailedException.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/EncryptFailedException.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/EncryptFailedException.java deleted file mode 100644 index 1dbc4cbb1..000000000 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/EncryptFailedException.java +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/MacAuthenticationFailedException.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/MacAuthenticationFailedException.java deleted file mode 100644 index 7535c35fe..000000000 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/MacAuthenticationFailedException.java +++ /dev/null @@ -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); - } - -} diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/MasterkeyDecryptionException.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/MasterkeyDecryptionException.java deleted file mode 100644 index 277215e2e..000000000 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/MasterkeyDecryptionException.java +++ /dev/null @@ -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); - } - -} diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedKeyLengthException.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedKeyLengthException.java deleted file mode 100644 index 548e05b6e..000000000 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedKeyLengthException.java +++ /dev/null @@ -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; - } - -} \ No newline at end of file diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedVaultException.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedVaultException.java deleted file mode 100644 index b2a69ac54..000000000 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedVaultException.java +++ /dev/null @@ -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; - } - -} diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/WrongPasswordException.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/WrongPasswordException.java deleted file mode 100644 index d9e1c83b4..000000000 --- a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/WrongPasswordException.java +++ /dev/null @@ -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."); - } -} \ No newline at end of file