Browse Source

- fixed warnings in OSX Finder
- improved performance by caching paths (no file contents get cached though)

Sebastian Stenzel 10 years ago
parent
commit
39d01c3106

+ 4 - 0
oce-main/oce-core/pom.xml

@@ -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>

+ 24 - 0
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<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);
+	}
+
+}

+ 31 - 3
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<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();
+	}
+
 }

+ 1 - 2
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

+ 1 - 1
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<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
 			}
 		}
 	}

+ 3 - 3
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<Long>(DavPropertyName.GETCONTENTLENGTH, contentLength));
 
 				final BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);

+ 7 - 7
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
 			}
 		};
 	}
+
 }

+ 30 - 0
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<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);
+	}
+
+}

+ 7 - 5
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 <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);
 
 }

+ 0 - 53
oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/MetadataSupport.java

@@ -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;
-	}
-
-}

+ 0 - 73
oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/NotACryptor.java

@@ -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.
-	}
-
-}

+ 11 - 0
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();
+
+}