mirror of
https://github.com/cryptomator/cryptomator.git
synced 2024-11-26 21:40:29 +00:00
- fixed warnings in OSX Finder
- improved performance by caching paths (no file contents get cached though)
This commit is contained in:
parent
9d6af97ef1
commit
39d01c3106
@ -65,6 +65,10 @@
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-collections4</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -0,0 +1,24 @@
|
||||
package de.sebastianstenzel.oce.webdav.jackrabbit;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.collections4.BidiMap;
|
||||
import org.apache.commons.collections4.bidimap.AbstractDualBidiMap;
|
||||
import org.apache.commons.collections4.map.LRUMap;
|
||||
|
||||
final class BidiLRUMap<K, V> extends AbstractDualBidiMap<K, V> {
|
||||
|
||||
public BidiLRUMap(int maxSize) {
|
||||
super(new LRUMap<K, V>(maxSize), new LRUMap<V, K>(maxSize));
|
||||
}
|
||||
|
||||
protected BidiLRUMap(final Map<K, V> normalMap, final Map<V, K> reverseMap, final BidiMap<V, K> inverseBidiMap) {
|
||||
super(normalMap, reverseMap, inverseBidiMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BidiMap<V, K> createBidiMap(Map<V, K> normalMap, Map<K, V> reverseMap, BidiMap<K, V> inverseMap) {
|
||||
return new BidiLRUMap<V, K>(normalMap, reverseMap, inverseMap);
|
||||
}
|
||||
|
||||
}
|
@ -15,16 +15,21 @@ import org.apache.jackrabbit.webdav.AbstractLocatorFactory;
|
||||
import org.apache.jackrabbit.webdav.DavResourceLocator;
|
||||
|
||||
import de.sebastianstenzel.oce.crypto.Cryptor;
|
||||
import de.sebastianstenzel.oce.crypto.SensitiveDataSwipeListener;
|
||||
|
||||
public class WebDavLocatorFactory extends AbstractLocatorFactory {
|
||||
public class WebDavLocatorFactory extends AbstractLocatorFactory implements SensitiveDataSwipeListener {
|
||||
|
||||
private static final int MAX_CACHED_PATHS = 10000;
|
||||
private final Path fsRoot;
|
||||
private final Cryptor cryptor;
|
||||
private final BidiLRUMap<String, String> pathCache; // <decryptedPath, encryptedPath>
|
||||
|
||||
public WebDavLocatorFactory(String fsRoot, String httpRoot, Cryptor cryptor) {
|
||||
super(httpRoot);
|
||||
this.fsRoot = FileSystems.getDefault().getPath(fsRoot);
|
||||
this.cryptor = cryptor;
|
||||
this.pathCache = new BidiLRUMap<>(MAX_CACHED_PATHS);
|
||||
cryptor.addSensitiveDataSwipeListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -32,10 +37,19 @@ public class WebDavLocatorFactory extends AbstractLocatorFactory {
|
||||
*/
|
||||
@Override
|
||||
protected String getRepositoryPath(String resourcePath, String wspPath) {
|
||||
String encryptedPath = pathCache.get(resourcePath);
|
||||
if (encryptedPath == null) {
|
||||
encryptedPath = encryptRepositoryPath(resourcePath);
|
||||
pathCache.put(resourcePath, encryptedPath);
|
||||
}
|
||||
return encryptedPath;
|
||||
}
|
||||
|
||||
private String encryptRepositoryPath(String resourcePath) {
|
||||
if (resourcePath == null) {
|
||||
return fsRoot.toString();
|
||||
}
|
||||
final String encryptedRepoPath = cryptor.encryptPath(resourcePath, FileSystems.getDefault().getSeparator().charAt(0), '/', null);
|
||||
final String encryptedRepoPath = cryptor.encryptPath(resourcePath, FileSystems.getDefault().getSeparator().charAt(0), '/');
|
||||
return fsRoot.resolve(encryptedRepoPath).toString();
|
||||
}
|
||||
|
||||
@ -44,12 +58,21 @@ public class WebDavLocatorFactory extends AbstractLocatorFactory {
|
||||
*/
|
||||
@Override
|
||||
protected String getResourcePath(String repositoryPath, String wspPath) {
|
||||
String decryptedPath = pathCache.getKey(repositoryPath);
|
||||
if (decryptedPath == null) {
|
||||
decryptedPath = decryptResourcePath(repositoryPath);
|
||||
pathCache.put(decryptedPath, repositoryPath);
|
||||
}
|
||||
return decryptedPath;
|
||||
}
|
||||
|
||||
private String decryptResourcePath(String repositoryPath) {
|
||||
final Path absRepoPath = FileSystems.getDefault().getPath(repositoryPath);
|
||||
if (fsRoot.equals(absRepoPath)) {
|
||||
return null;
|
||||
} else {
|
||||
final Path relativeRepositoryPath = fsRoot.relativize(absRepoPath);
|
||||
final String resourcePath = cryptor.decryptPath(relativeRepositoryPath.toString(), FileSystems.getDefault().getSeparator().charAt(0), '/', null);
|
||||
final String resourcePath = cryptor.decryptPath(relativeRepositoryPath.toString(), FileSystems.getDefault().getSeparator().charAt(0), '/');
|
||||
return resourcePath;
|
||||
}
|
||||
}
|
||||
@ -66,4 +89,9 @@ public class WebDavLocatorFactory extends AbstractLocatorFactory {
|
||||
return super.createResourceLocator(prefix, "", resourcePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swipeSensitiveData() {
|
||||
pathCache.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -50,8 +50,7 @@ public class WebDavServlet extends AbstractWebdavServlet {
|
||||
|
||||
@Override
|
||||
protected boolean isPreconditionValid(WebdavRequest request, DavResource resource) {
|
||||
// TODO Auto-generated method stub
|
||||
return true;
|
||||
return !resource.exists() || request.matchesIfHeader(resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -144,7 +144,7 @@ public class EncryptedDir extends AbstractEncryptedNode {
|
||||
properties.add(new DefaultDavProperty<Long>(DavPropertyName.GETLASTMODIFIED, attrs.lastModifiedTime().toMillis()));
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error determining metadata " + path.toString(), e);
|
||||
throw new IORuntimeException(e);
|
||||
// don't add any further properties
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
* Sebastian Stenzel - initial API and implementation
|
||||
******************************************************************************/
|
||||
package de.sebastianstenzel.oce.webdav.jackrabbit.resources;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
@ -33,7 +34,6 @@ import org.slf4j.LoggerFactory;
|
||||
import de.sebastianstenzel.oce.crypto.Cryptor;
|
||||
import de.sebastianstenzel.oce.webdav.exceptions.IORuntimeException;
|
||||
|
||||
|
||||
public class EncryptedFile extends AbstractEncryptedNode {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(EncryptedFile.class);
|
||||
@ -70,7 +70,7 @@ public class EncryptedFile extends AbstractEncryptedNode {
|
||||
SeekableByteChannel channel = null;
|
||||
try {
|
||||
channel = Files.newByteChannel(path, StandardOpenOption.READ);
|
||||
outputContext.setContentLength(cryptor.decryptedContentLength(channel, null));
|
||||
outputContext.setContentLength(cryptor.decryptedContentLength(channel));
|
||||
if (outputContext.hasStream()) {
|
||||
cryptor.decryptedFile(channel, outputContext.getOutputStream());
|
||||
}
|
||||
@ -93,7 +93,7 @@ public class EncryptedFile extends AbstractEncryptedNode {
|
||||
SeekableByteChannel channel = null;
|
||||
try {
|
||||
channel = Files.newByteChannel(path, StandardOpenOption.READ);
|
||||
final Long contentLength = cryptor.decryptedContentLength(channel, null);
|
||||
final Long contentLength = cryptor.decryptedContentLength(channel);
|
||||
properties.add(new DefaultDavProperty<Long>(DavPropertyName.GETCONTENTLENGTH, contentLength));
|
||||
|
||||
final BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
|
||||
|
@ -44,15 +44,14 @@ import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import de.sebastianstenzel.oce.crypto.Cryptor;
|
||||
import de.sebastianstenzel.oce.crypto.MetadataSupport;
|
||||
import de.sebastianstenzel.oce.crypto.AbstractCryptor;
|
||||
import de.sebastianstenzel.oce.crypto.exceptions.DecryptFailedException;
|
||||
import de.sebastianstenzel.oce.crypto.exceptions.UnsupportedKeyLengthException;
|
||||
import de.sebastianstenzel.oce.crypto.exceptions.WrongPasswordException;
|
||||
import de.sebastianstenzel.oce.crypto.io.SeekableByteChannelInputStream;
|
||||
import de.sebastianstenzel.oce.crypto.io.SeekableByteChannelOutputStream;
|
||||
|
||||
public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, FileNamingConventions {
|
||||
public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicConfiguration, FileNamingConventions {
|
||||
|
||||
/**
|
||||
* PRNG for cryptographically secure random numbers. Defaults to SHA1-based number generator.
|
||||
@ -189,7 +188,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
|
||||
* Otherwise developers could accidentally just assign a new object to the variable.
|
||||
*/
|
||||
@Override
|
||||
public void swipeSensitiveData() {
|
||||
public void swipeSensitiveDataInternal() {
|
||||
Arrays.fill(this.masterKey, (byte) 0);
|
||||
}
|
||||
|
||||
@ -248,7 +247,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encryptPath(String cleartextPath, char encryptedPathSep, char cleartextPathSep, MetadataSupport metadataSupport) {
|
||||
public String encryptPath(String cleartextPath, char encryptedPathSep, char cleartextPathSep) {
|
||||
try {
|
||||
final SecretKey key = this.pbkdf2(masterKey, EMPTY_SALT, PBKDF2_MASTERKEY_ITERATIONS, AES_KEY_LENGTH);
|
||||
final String[] cleartextPathComps = StringUtils.split(cleartextPath, cleartextPathSep);
|
||||
@ -282,7 +281,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decryptPath(String encryptedPath, char encryptedPathSep, char cleartextPathSep, MetadataSupport metadataSupport) {
|
||||
public String decryptPath(String encryptedPath, char encryptedPathSep, char cleartextPathSep) {
|
||||
try {
|
||||
final SecretKey key = this.pbkdf2(masterKey, EMPTY_SALT, PBKDF2_MASTERKEY_ITERATIONS, AES_KEY_LENGTH);
|
||||
final String[] encryptedPathComps = StringUtils.split(encryptedPath, encryptedPathSep);
|
||||
@ -320,7 +319,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long decryptedContentLength(SeekableByteChannel encryptedFile, MetadataSupport metadataSupport) throws IOException {
|
||||
public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException {
|
||||
final ByteBuffer sizeBuffer = ByteBuffer.allocate(SIZE_OF_LONG);
|
||||
final int read = encryptedFile.read(sizeBuffer);
|
||||
if (read == SIZE_OF_LONG) {
|
||||
@ -395,4 +394,5 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
package de.sebastianstenzel.oce.crypto;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class AbstractCryptor implements Cryptor {
|
||||
|
||||
private final Set<SensitiveDataSwipeListener> swipeListeners = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public final void swipeSensitiveData() {
|
||||
this.swipeSensitiveDataInternal();
|
||||
for (final SensitiveDataSwipeListener sensitiveDataSwipeListener : swipeListeners) {
|
||||
sensitiveDataSwipeListener.swipeSensitiveData();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void swipeSensitiveDataInternal();
|
||||
|
||||
@Override
|
||||
public final void addSensitiveDataSwipeListener(SensitiveDataSwipeListener listener) {
|
||||
this.swipeListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void removeSensitiveDataSwipeListener(SensitiveDataSwipeListener listener) {
|
||||
this.swipeListeners.remove(listener);
|
||||
}
|
||||
|
||||
}
|
@ -18,7 +18,7 @@ import java.nio.file.Path;
|
||||
/**
|
||||
* Provides access to cryptographic functions. All methods are threadsafe.
|
||||
*/
|
||||
public interface Cryptor {
|
||||
public interface Cryptor extends SensitiveDataSwipeListener {
|
||||
|
||||
/**
|
||||
* Encrypts each plaintext path component for its own.
|
||||
@ -32,7 +32,7 @@ public interface Cryptor {
|
||||
* @return Encrypted path components concatenated by the given encryptedPathSep. Must not start with encryptedPathSep, unless the
|
||||
* encrypted path is explicitly absolute.
|
||||
*/
|
||||
String encryptPath(String cleartextPath, char encryptedPathSep, char cleartextPathSep, MetadataSupport metadataSupport);
|
||||
String encryptPath(String cleartextPath, char encryptedPathSep, char cleartextPathSep);
|
||||
|
||||
/**
|
||||
* Decrypts each encrypted path component for its own.
|
||||
@ -46,13 +46,13 @@ public interface Cryptor {
|
||||
* @return Decrypted path components concatenated by the given cleartextPathSep. Must not start with cleartextPathSep, unless the
|
||||
* cleartext path is explicitly absolute.
|
||||
*/
|
||||
String decryptPath(String encryptedPath, char encryptedPathSep, char cleartextPathSep, MetadataSupport metadataSupport);
|
||||
String decryptPath(String encryptedPath, char encryptedPathSep, char cleartextPathSep);
|
||||
|
||||
/**
|
||||
* @param metadataSupport Support object allowing the Cryptor to read and write its own metadata to the location of the encrypted file.
|
||||
* @return Content length of the decrypted file or <code>null</code> if unknown.
|
||||
*/
|
||||
Long decryptedContentLength(SeekableByteChannel encryptedFile, MetadataSupport metadataSupport) throws IOException;
|
||||
Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException;
|
||||
|
||||
/**
|
||||
* @return Number of decrypted bytes. This might not be equal to the encrypted file size due to optional metadata written to it.
|
||||
@ -70,6 +70,8 @@ public interface Cryptor {
|
||||
*/
|
||||
Filter<Path> getPayloadFilesFilter();
|
||||
|
||||
void swipeSensitiveData();
|
||||
void addSensitiveDataSwipeListener(SensitiveDataSwipeListener listener);
|
||||
|
||||
void removeSensitiveDataSwipeListener(SensitiveDataSwipeListener listener);
|
||||
|
||||
}
|
||||
|
@ -1,53 +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 de.sebastianstenzel.oce.crypto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
|
||||
@Deprecated
|
||||
public interface MetadataSupport {
|
||||
|
||||
/**
|
||||
* Opens the file with the given name for writing. Overwrites existing files.
|
||||
*
|
||||
* @return Outputstream ready to write to. Must be closed by caller.
|
||||
* @throws IOException
|
||||
*/
|
||||
OutputStream openMetadataForWrite(String filename, Level location) throws IOException;
|
||||
|
||||
/**
|
||||
* Opens the file with the given name without locking it.
|
||||
*
|
||||
* @return InputStream ready to read from. Must be closed by caller.
|
||||
* @throws NoSuchFileException
|
||||
* @throws IOException
|
||||
*/
|
||||
InputStream openMetadataForRead(String filename, Level location) throws NoSuchFileException, IOException;
|
||||
|
||||
enum Level {
|
||||
/**
|
||||
* Root folder of the encrypted store.
|
||||
*/
|
||||
ROOT_FOLDER,
|
||||
|
||||
/**
|
||||
* Parent folder of the current object.
|
||||
*/
|
||||
PARENT_FOLDER,
|
||||
|
||||
/**
|
||||
* If the current object is a folder, its own location. If the current object is a file, behaves the same as {@link #PARENT_FOLDER}.
|
||||
*/
|
||||
CURRENT_FOLDER;
|
||||
}
|
||||
|
||||
}
|
@ -1,73 +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 de.sebastianstenzel.oce.crypto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.DirectoryStream.Filter;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import de.sebastianstenzel.oce.crypto.io.SeekableByteChannelInputStream;
|
||||
import de.sebastianstenzel.oce.crypto.io.SeekableByteChannelOutputStream;
|
||||
|
||||
/**
|
||||
* @deprecated Just for test purposes.
|
||||
*/
|
||||
@Deprecated
|
||||
public class NotACryptor implements Cryptor {
|
||||
|
||||
@Override
|
||||
public String encryptPath(String cleartextPath, char encryptedPathSep, char cleartextPathSep, MetadataSupport metadataSupport) {
|
||||
return cleartextPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decryptPath(String encryptedPath, char encryptedPathSep, char cleartextPathSep, MetadataSupport metadataSupport) {
|
||||
return encryptedPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long decryptedContentLength(SeekableByteChannel encryptedFile, MetadataSupport metadataSupport) throws IOException {
|
||||
return encryptedFile.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException {
|
||||
final InputStream in = new SeekableByteChannelInputStream(encryptedFile);
|
||||
return IOUtils.copyLarge(in, plaintextFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long encryptFile(InputStream plaintextFile, SeekableByteChannel encryptedFile) throws IOException {
|
||||
final OutputStream out = new SeekableByteChannelOutputStream(encryptedFile);
|
||||
return IOUtils.copyLarge(plaintextFile, out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter<Path> getPayloadFilesFilter() {
|
||||
return new Filter<Path>() {
|
||||
|
||||
@Override
|
||||
public boolean accept(Path entry) throws IOException {
|
||||
/* all files are "encrypted" */
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void swipeSensitiveData() {
|
||||
// do nothing.
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package de.sebastianstenzel.oce.crypto;
|
||||
|
||||
public interface SensitiveDataSwipeListener {
|
||||
|
||||
/**
|
||||
* Removes sensitive data from memory. Depending on the data (e.g. for passwords) it might be necessary to overwrite the memory before
|
||||
* freeing the object.
|
||||
*/
|
||||
void swipeSensitiveData();
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user