- fixed warnings in OSX Finder

- improved performance by caching paths (no file contents get cached though)
This commit is contained in:
Sebastian Stenzel 2014-11-04 10:03:19 +01:00
parent 9d6af97ef1
commit 39d01c3106
12 changed files with 119 additions and 147 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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