refactored directory structure, so windows (and OneDrive) can handle vaults better

This commit is contained in:
Sebastian Stenzel
2015-04-28 18:19:05 +02:00
parent a6972f62f2
commit cdf9c28a38
31 changed files with 888 additions and 728 deletions
+4
View File
@@ -27,5 +27,9 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
</dependencies>
</project>
@@ -1,38 +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 org.cryptomator.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);
}
}
@@ -0,0 +1,90 @@
package org.cryptomator.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 javax.security.auth.DestroyFailedException;
import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.crypto.exceptions.EncryptFailedException;
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
import org.cryptomator.crypto.exceptions.WrongPasswordException;
public class AbstractCryptorDecorator implements Cryptor {
protected final Cryptor cryptor;
public AbstractCryptorDecorator(Cryptor cryptor) {
this.cryptor = cryptor;
}
@Override
public void encryptMasterKey(OutputStream out, CharSequence password) throws IOException {
cryptor.encryptMasterKey(out, password);
}
@Override
public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException {
cryptor.decryptMasterKey(in, password);
}
@Override
public String encryptDirectoryPath(String cleartextPath, String nativePathSep) {
return cryptor.encryptDirectoryPath(cleartextPath, nativePathSep);
}
@Override
public String encryptFilename(String cleartextName, CryptorMetadataSupport ioSupport) throws IOException {
return cryptor.encryptFilename(cleartextName, ioSupport);
}
@Override
public String decryptFilename(String ciphertextName, CryptorMetadataSupport ioSupport) throws IOException, DecryptFailedException {
return cryptor.decryptFilename(ciphertextName, ioSupport);
}
@Override
public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException {
return cryptor.decryptedContentLength(encryptedFile);
}
@Override
public boolean isAuthentic(SeekableByteChannel encryptedFile) throws IOException {
return cryptor.isAuthentic(encryptedFile);
}
@Override
public Long decryptFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException, DecryptFailedException {
return cryptor.decryptFile(encryptedFile, plaintextFile);
}
@Override
public Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length) throws IOException, DecryptFailedException {
return cryptor.decryptRange(encryptedFile, plaintextFile, pos, length);
}
@Override
public Long encryptFile(InputStream plaintextFile, SeekableByteChannel encryptedFile) throws IOException, EncryptFailedException {
return cryptor.encryptFile(plaintextFile, encryptedFile);
}
@Override
public Filter<Path> getPayloadFilesFilter() {
return cryptor.getPayloadFilesFilter();
}
@Override
public void destroy() throws DestroyFailedException {
cryptor.destroy();
}
@Override
public boolean isDestroyed() {
return cryptor.isDestroyed();
}
}
@@ -15,6 +15,8 @@ import java.nio.channels.SeekableByteChannel;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.Path;
import javax.security.auth.Destroyable;
import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.crypto.exceptions.EncryptFailedException;
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
@@ -23,7 +25,7 @@ import org.cryptomator.crypto.exceptions.WrongPasswordException;
/**
* Provides access to cryptographic functions. All methods are threadsafe.
*/
public interface Cryptor extends SensitiveDataSwipeListener {
public interface Cryptor extends Destroyable {
/**
* Encrypts the current masterKey with the given password and writes the result to the given output stream.
@@ -42,33 +44,38 @@ public interface Cryptor extends SensitiveDataSwipeListener {
void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException;
/**
* Encrypts each plaintext path component for its own.
* Encrypts a given plaintext path representing a directory structure. See {@link #encryptFilename(String, CryptorMetadataSupport)} for
* contents inside directories.
*
* @param cleartextPath A relative path (UTF-8 encoded)
* @param encryptedPathSep Path separator char like '/' used on local file system. Must not be null, even if cleartextPath is a sole
* file name without any path separators.
* @param cleartextPathSep Path separator char like '/' used in webdav URIs. Must not be null, even if cleartextPath is a sole file name
* @param cleartextPath A relative path (UTF-8 encoded), whose path components are separated by '/'
* @param nativePathSep Path separator like "/" used on local file system. Must not be null, even if cleartextPath is a sole file name
* without any path separators.
* @param metadataSupport Support object allowing the Cryptor to read and write its own metadata to the location of the encrypted file.
* @return Encrypted path components concatenated by the given encryptedPathSep. Must not start with encryptedPathSep, unless the
* encrypted path is explicitly absolute.
* @return Encrypted path.
*/
String encryptPath(String cleartextPath, char encryptedPathSep, char cleartextPathSep, CryptorIOSupport ioSupport);
String encryptDirectoryPath(String cleartextPath, String nativePathSep);
/**
* Decrypts each encrypted path component for its own.
* Encrypts the name of a file. See {@link #encryptDirectoryPath(String, char)} for parent dir.
*
* @param encryptedPath A relative path (UTF-8 encoded)
* @param encryptedPathSep Path separator char like '/' used on local file system. Must not be null, even if encryptedPath is a sole
* file name without any path separators.
* @param cleartextPathSep Path separator char like '/' used in webdav URIs. Must not be null, even if encryptedPath is a sole file name
* without any path separators.
* @param metadataSupport Support object allowing the Cryptor to read and write its own metadata to the location of the encrypted file.
* @return Decrypted path components concatenated by the given cleartextPathSep. Must not start with cleartextPathSep, unless the
* cleartext path is explicitly absolute.
* @throws DecryptFailedException If the decryption failed for various reasons (including wrong password).
* @param cleartextName A plaintext filename without any preceeding directory paths.
* @param ioSupport Support object allowing the Cryptor to read and write its own metadata to a storage space associated with this
* support object.
* @return Encrypted filename.
* @throws IOException If ioSupport throws an IOException
*/
String decryptPath(String encryptedPath, char encryptedPathSep, char cleartextPathSep, CryptorIOSupport ioSupport) throws DecryptFailedException;
String encryptFilename(String cleartextName, CryptorMetadataSupport ioSupport) throws IOException;
/**
* Decrypts the name of a file.
*
* @param ciphertextName A ciphertext filename without any preceeding directory paths.
* @param ioSupport Support object allowing the Cryptor to read and write its own metadata to a storage space associated with this
* support object.
* @return Decrypted filename.
* @throws DecryptFailedException If the decryption failed for various reasons (including wrong password).
* @throws IOException If ioSupport throws an IOException
*/
String decryptFilename(String ciphertextName, CryptorMetadataSupport ioSupport) throws IOException, DecryptFailedException;
/**
* @param metadataSupport Support object allowing the Cryptor to read and write its own metadata to the location of the encrypted file.
@@ -106,8 +113,4 @@ public interface Cryptor extends SensitiveDataSwipeListener {
*/
Filter<Path> getPayloadFilesFilter();
void addSensitiveDataSwipeListener(SensitiveDataSwipeListener listener);
void removeSensitiveDataSwipeListener(SensitiveDataSwipeListener listener);
}
@@ -13,19 +13,19 @@ import java.io.IOException;
/**
* Methods that may be called by the Cryptor when accessing a path.
*/
public interface CryptorIOSupport {
public interface CryptorMetadataSupport {
/**
* Persists encryptedMetadata to the given encryptedPath.
* Persists encryptedMetadata in a metadata group.
*
* @param encryptedPath A relative path
* @param metadataFilename File relative to
* @throws IOException
*/
void writePathSpecificMetadata(String encryptedPath, byte[] encryptedMetadata) throws IOException;
void writeMetadata(String metadataGroup, byte[] encryptedMetadata) throws IOException;
/**
* @return Previously written encryptedMetadata stored at the given encryptedPath or <code>null</code> if no such file exists.
* @return Previously written metadata stored in the given metadata group or <code>null</code> if no such group exists.
*/
byte[] readPathSpecificMetadata(String encryptedPath) throws IOException;
byte[] readMetadata(String metadataGroup) throws IOException;
}
@@ -0,0 +1,79 @@
package org.cryptomator.crypto;
import java.io.IOException;
import java.util.Map;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.AbstractDualBidiMap;
import org.apache.commons.collections4.map.LRUMap;
import org.cryptomator.crypto.exceptions.DecryptFailedException;
public class PathCachingCryptorDecorator extends AbstractCryptorDecorator {
private static final int MAX_CACHED_PATHS = 5000;
private static final int MAX_CACHED_NAMES = 5000;
private final Map<String, String> pathCache = new LRUMap<>(MAX_CACHED_PATHS); // <cleartextPath, ciphertextPath>
private final BidiMap<String, String> nameCache = new BidiLRUMap<>(MAX_CACHED_NAMES); // <cleartextName, ciphertextName>
private PathCachingCryptorDecorator(Cryptor cryptor) {
super(cryptor);
}
public static Cryptor decorate(Cryptor cryptor) {
return new PathCachingCryptorDecorator(cryptor);
}
/* Cryptor */
@Override
public String encryptDirectoryPath(String cleartextPath, String nativePathSep) {
if (pathCache.containsKey(cleartextPath)) {
return pathCache.get(cleartextPath);
} else {
final String ciphertextPath = cryptor.encryptDirectoryPath(cleartextPath, nativePathSep);
pathCache.put(cleartextPath, ciphertextPath);
return ciphertextPath;
}
}
@Override
public String encryptFilename(String cleartextName, CryptorMetadataSupport ioSupport) throws IOException {
if (nameCache.containsKey(cleartextName)) {
return nameCache.get(cleartextName);
} else {
final String ciphertextName = cryptor.encryptFilename(cleartextName, ioSupport);
nameCache.put(cleartextName, ciphertextName);
return ciphertextName;
}
}
@Override
public String decryptFilename(String ciphertextName, CryptorMetadataSupport ioSupport) throws IOException, DecryptFailedException {
if (nameCache.containsValue(ciphertextName)) {
return nameCache.getKey(ciphertextName);
} else {
final String cleartextName = cryptor.decryptFilename(ciphertextName, ioSupport);
nameCache.put(cleartextName, ciphertextName);
return ciphertextName;
}
}
private static class BidiLRUMap<K, V> extends AbstractDualBidiMap<K, V> {
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);
}
}
}
@@ -4,35 +4,24 @@ 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 java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.lang3.StringUtils;
import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.crypto.exceptions.EncryptFailedException;
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
import org.cryptomator.crypto.exceptions.WrongPasswordException;
public class SamplingDecorator implements Cryptor, CryptorIOSampling {
public class SamplingCryptorDecorator extends AbstractCryptorDecorator implements CryptorIOSampling {
private final Cryptor cryptor;
private final AtomicLong encryptedBytes;
private final AtomicLong decryptedBytes;
private SamplingDecorator(Cryptor cryptor) {
this.cryptor = cryptor;
private SamplingCryptorDecorator(Cryptor cryptor) {
super(cryptor);
encryptedBytes = new AtomicLong();
decryptedBytes = new AtomicLong();
}
public static Cryptor decorate(Cryptor cryptor) {
return new SamplingDecorator(cryptor);
}
@Override
public void swipeSensitiveData() {
cryptor.swipeSensitiveData();
return new SamplingCryptorDecorator(cryptor);
}
@Override
@@ -55,38 +44,6 @@ public class SamplingDecorator implements Cryptor, CryptorIOSampling {
/* Cryptor */
@Override
public void encryptMasterKey(OutputStream out, CharSequence password) throws IOException {
cryptor.encryptMasterKey(out, password);
}
@Override
public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException {
cryptor.decryptMasterKey(in, password);
}
@Override
public String encryptPath(String cleartextPath, char encryptedPathSep, char cleartextPathSep, CryptorIOSupport ioSupport) {
encryptedBytes.addAndGet(StringUtils.length(cleartextPath));
return cryptor.encryptPath(cleartextPath, encryptedPathSep, cleartextPathSep, ioSupport);
}
@Override
public String decryptPath(String encryptedPath, char encryptedPathSep, char cleartextPathSep, CryptorIOSupport ioSupport) throws DecryptFailedException {
decryptedBytes.addAndGet(StringUtils.length(encryptedPath));
return cryptor.decryptPath(encryptedPath, encryptedPathSep, cleartextPathSep, ioSupport);
}
@Override
public Long decryptedContentLength(SeekableByteChannel encryptedFile) throws IOException {
return cryptor.decryptedContentLength(encryptedFile);
}
@Override
public boolean isAuthentic(SeekableByteChannel encryptedFile) throws IOException {
return cryptor.isAuthentic(encryptedFile);
}
@Override
public Long decryptFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException, DecryptFailedException {
final OutputStream countingInputStream = new CountingOutputStream(decryptedBytes, plaintextFile);
@@ -105,21 +62,6 @@ public class SamplingDecorator implements Cryptor, CryptorIOSampling {
return cryptor.encryptFile(countingInputStream, encryptedFile);
}
@Override
public Filter<Path> getPayloadFilesFilter() {
return cryptor.getPayloadFilesFilter();
}
@Override
public void addSensitiveDataSwipeListener(SensitiveDataSwipeListener listener) {
cryptor.addSensitiveDataSwipeListener(listener);
}
@Override
public void removeSensitiveDataSwipeListener(SensitiveDataSwipeListener listener) {
cryptor.removeSensitiveDataSwipeListener(listener);
}
private class CountingInputStream extends InputStream {
private final InputStream in;
@@ -1,19 +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 org.cryptomator.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();
}