diff --git a/oce-main/oce-core/pom.xml b/oce-main/oce-core/pom.xml
index cb63a1a99..4a1310b41 100644
--- a/oce-main/oce-core/pom.xml
+++ b/oce-main/oce-core/pom.xml
@@ -65,6 +65,10 @@
org.apache.commons
commons-lang3
+
+ org.apache.commons
+ commons-collections4
+
diff --git a/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/BidiLRUMap.java b/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/BidiLRUMap.java
new file mode 100644
index 000000000..c5924735d
--- /dev/null
+++ b/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/BidiLRUMap.java
@@ -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 extends AbstractDualBidiMap {
+
+ public 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/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/WebDavLocatorFactory.java b/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/WebDavLocatorFactory.java
index d80fe7977..c79d94a61 100644
--- a/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/WebDavLocatorFactory.java
+++ b/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/WebDavLocatorFactory.java
@@ -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 pathCache; //
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();
+ }
+
}
diff --git a/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/WebDavServlet.java b/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/WebDavServlet.java
index ca56e12a4..858b12337 100644
--- a/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/WebDavServlet.java
+++ b/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/WebDavServlet.java
@@ -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
diff --git a/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/resources/EncryptedDir.java b/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/resources/EncryptedDir.java
index 35cfe6d4a..f7c4e7f74 100644
--- a/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/resources/EncryptedDir.java
+++ b/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/resources/EncryptedDir.java
@@ -144,7 +144,7 @@ public class EncryptedDir extends AbstractEncryptedNode {
properties.add(new DefaultDavProperty(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
}
}
}
diff --git a/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/resources/EncryptedFile.java b/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/resources/EncryptedFile.java
index 8d7ca8d51..59aa9f70c 100644
--- a/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/resources/EncryptedFile.java
+++ b/oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/resources/EncryptedFile.java
@@ -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(DavPropertyName.GETCONTENTLENGTH, contentLength));
final BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
diff --git a/oce-main/oce-crypto-aes/src/main/java/de/sebastianstenzel/oce/crypto/aes256/Aes256Cryptor.java b/oce-main/oce-crypto-aes/src/main/java/de/sebastianstenzel/oce/crypto/aes256/Aes256Cryptor.java
index 5db9e5471..5b5481338 100644
--- a/oce-main/oce-crypto-aes/src/main/java/de/sebastianstenzel/oce/crypto/aes256/Aes256Cryptor.java
+++ b/oce-main/oce-crypto-aes/src/main/java/de/sebastianstenzel/oce/crypto/aes256/Aes256Cryptor.java
@@ -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
}
};
}
+
}
diff --git a/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/AbstractCryptor.java b/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/AbstractCryptor.java
new file mode 100644
index 000000000..05d055602
--- /dev/null
+++ b/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/AbstractCryptor.java
@@ -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 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);
+ }
+
+}
diff --git a/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/Cryptor.java b/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/Cryptor.java
index a2053b1ae..63a5a0a17 100644
--- a/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/Cryptor.java
+++ b/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/Cryptor.java
@@ -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 null 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 getPayloadFilesFilter();
- void swipeSensitiveData();
+ void addSensitiveDataSwipeListener(SensitiveDataSwipeListener listener);
+
+ void removeSensitiveDataSwipeListener(SensitiveDataSwipeListener listener);
}
diff --git a/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/MetadataSupport.java b/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/MetadataSupport.java
deleted file mode 100644
index f42533525..000000000
--- a/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/MetadataSupport.java
+++ /dev/null
@@ -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;
- }
-
-}
diff --git a/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/NotACryptor.java b/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/NotACryptor.java
deleted file mode 100644
index 102d42fc3..000000000
--- a/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/NotACryptor.java
+++ /dev/null
@@ -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 getPayloadFilesFilter() {
- return new Filter() {
-
- @Override
- public boolean accept(Path entry) throws IOException {
- /* all files are "encrypted" */
- return true;
- }
- };
- }
-
- @Override
- public void swipeSensitiveData() {
- // do nothing.
- }
-
-}
diff --git a/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/SensitiveDataSwipeListener.java b/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/SensitiveDataSwipeListener.java
new file mode 100644
index 000000000..5abbd66c1
--- /dev/null
+++ b/oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/SensitiveDataSwipeListener.java
@@ -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();
+
+}