From 39d01c310698492331e4083f7e5f25f8848b88d8 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 4 Nov 2014 10:03:19 +0100 Subject: [PATCH] - fixed warnings in OSX Finder - improved performance by caching paths (no file contents get cached though) --- oce-main/oce-core/pom.xml | 4 + .../oce/webdav/jackrabbit/BidiLRUMap.java | 24 ++++++ .../jackrabbit/WebDavLocatorFactory.java | 34 ++++++++- .../oce/webdav/jackrabbit/WebDavServlet.java | 3 +- .../jackrabbit/resources/EncryptedDir.java | 2 +- .../jackrabbit/resources/EncryptedFile.java | 6 +- .../oce/crypto/aes256/Aes256Cryptor.java | 14 ++-- .../oce/crypto/AbstractCryptor.java | 30 ++++++++ .../sebastianstenzel/oce/crypto/Cryptor.java | 12 +-- .../oce/crypto/MetadataSupport.java | 53 -------------- .../oce/crypto/NotACryptor.java | 73 ------------------- .../crypto/SensitiveDataSwipeListener.java | 11 +++ 12 files changed, 119 insertions(+), 147 deletions(-) create mode 100644 oce-main/oce-core/src/main/java/de/sebastianstenzel/oce/webdav/jackrabbit/BidiLRUMap.java create mode 100644 oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/AbstractCryptor.java delete mode 100644 oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/MetadataSupport.java delete mode 100644 oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/NotACryptor.java create mode 100644 oce-main/oce-crypto-api/src/main/java/de/sebastianstenzel/oce/crypto/SensitiveDataSwipeListener.java 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(); + +}