diff --git a/main/crypto-layer/pom.xml b/main/crypto-layer/pom.xml
index 4f74e5233..5fe215908 100644
--- a/main/crypto-layer/pom.xml
+++ b/main/crypto-layer/pom.xml
@@ -1,6 +1,6 @@
+
+ org.cryptomator
+ siv-mode
+ ${sivmode.version}
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+ ${bouncycastle.version}
+
+
+
org.apache.commons
commons-lang3
@@ -31,5 +49,12 @@
commons-codec
commons-codec
+
+
+
+ org.cryptomator
+ filesystem-inmemory
+ test
+
\ No newline at end of file
diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/CryptoException.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/CryptoException.java
new file mode 100644
index 000000000..42054592c
--- /dev/null
+++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/CryptoException.java
@@ -0,0 +1,13 @@
+package org.cryptomator.crypto.engine;
+
+import java.io.IOException;
+
+public class CryptoException extends IOException {
+
+ private static final long serialVersionUID = -6536997506620449023L;
+
+ public CryptoException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/Cryptor.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/Cryptor.java
new file mode 100644
index 000000000..c1cc321e3
--- /dev/null
+++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/Cryptor.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.engine;
+
+import javax.security.auth.Destroyable;
+
+/**
+ * A Cryptor instance, once initialized with a set of keys, provides access to threadsafe cryptographic routines.
+ */
+public interface Cryptor extends Destroyable {
+
+ FilenameCryptor getFilenameCryptor();
+
+}
diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/FilenameCryptor.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/FilenameCryptor.java
new file mode 100644
index 000000000..38e4c3c19
--- /dev/null
+++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/FilenameCryptor.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.engine;
+
+import javax.security.auth.Destroyable;
+
+/**
+ * Provides deterministic encryption capabilities as filenames must not change on subsequent encryption attempts,
+ * otherwise each change results in major directory structure changes which would be a terrible idea for cloud storage encryption.
+ *
+ * @see Wikipedia on deterministic encryption
+ */
+public interface FilenameCryptor extends Destroyable {
+
+ /**
+ * @return constant length string, that is unlikely to collide with any other name.
+ */
+ String hashDirectoryId(String cleartextDirectoryId);
+
+ /**
+ * @param cleartextName original filename including cleartext file extension
+ * @return encrypted filename without any file extension
+ */
+ String encryptFilename(String cleartextName);
+
+ /**
+ * @param ciphertextName Ciphertext only, with any additional strings like file extensions stripped first.
+ * @return cleartext filename, probably including its cleartext file extension.
+ */
+ String decryptFilename(String ciphertextName);
+}
diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java
new file mode 100644
index 000000000..f6dfd94b0
--- /dev/null
+++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java
@@ -0,0 +1,40 @@
+package org.cryptomator.crypto.engine.impl;
+
+import javax.crypto.SecretKey;
+import javax.security.auth.DestroyFailedException;
+
+import org.cryptomator.crypto.engine.Cryptor;
+import org.cryptomator.crypto.engine.FilenameCryptor;
+
+public class CryptorImpl implements Cryptor {
+
+ private final SecretKey encryptionKey;
+ private final SecretKey macKey;
+ private final FilenameCryptor filenameCryptor;
+
+ public CryptorImpl(SecretKey encryptionKey, SecretKey macKey) {
+ this.encryptionKey = encryptionKey;
+ this.macKey = macKey;
+ this.filenameCryptor = new FilenameCryptorImpl(encryptionKey, macKey);
+ }
+
+ @Override
+ public FilenameCryptor getFilenameCryptor() {
+ return filenameCryptor;
+ }
+
+ /* ======================= destruction ======================= */
+
+ @Override
+ public void destroy() throws DestroyFailedException {
+ TheDestroyer.destroyQuietly(encryptionKey);
+ TheDestroyer.destroyQuietly(macKey);
+ TheDestroyer.destroyQuietly(filenameCryptor);
+ }
+
+ @Override
+ public boolean isDestroyed() {
+ return encryptionKey.isDestroyed() && macKey.isDestroyed() && filenameCryptor.isDestroyed();
+ }
+
+}
diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java
new file mode 100644
index 000000000..238034f90
--- /dev/null
+++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImpl.java
@@ -0,0 +1,90 @@
+package org.cryptomator.crypto.engine.impl;
+
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.AEADBadTagException;
+import javax.crypto.SecretKey;
+import javax.security.auth.DestroyFailedException;
+
+import org.apache.commons.codec.binary.Base32;
+import org.apache.commons.codec.binary.BaseNCodec;
+import org.cryptomator.crypto.engine.CryptoException;
+import org.cryptomator.crypto.engine.FilenameCryptor;
+import org.cryptomator.siv.SivMode;
+
+class FilenameCryptorImpl implements FilenameCryptor {
+
+ private static final BaseNCodec BASE32 = new Base32();
+ private static final ThreadLocal SHA256 = new ThreadLocalSha256();
+ private static final SivMode AES_SIV = new SivMode();
+
+ private final SecretKey encryptionKey;
+ private final SecretKey macKey;
+
+ FilenameCryptorImpl(SecretKey encryptionKey, SecretKey macKey) {
+ this.encryptionKey = encryptionKey;
+ this.macKey = macKey;
+ }
+
+ @Override
+ public String hashDirectoryId(String cleartextDirectoryId) {
+ final byte[] cleartextBytes = cleartextDirectoryId.getBytes(StandardCharsets.UTF_8);
+ byte[] encryptedBytes = AES_SIV.encrypt(encryptionKey, macKey, cleartextBytes);
+ final byte[] hashedBytes = SHA256.get().digest(encryptedBytes);
+ return BASE32.encodeAsString(hashedBytes);
+ }
+
+ @Override
+ public String encryptFilename(String cleartextName) {
+ final byte[] cleartextBytes = cleartextName.getBytes(StandardCharsets.UTF_8);
+ final byte[] encryptedBytes = AES_SIV.encrypt(encryptionKey, macKey, cleartextBytes);
+ return BASE32.encodeAsString(encryptedBytes);
+ }
+
+ @Override
+ public String decryptFilename(String ciphertextName) {
+ final byte[] encryptedBytes = BASE32.decode(ciphertextName);
+ try {
+ final byte[] cleartextBytes = AES_SIV.decrypt(encryptionKey, macKey, encryptedBytes);
+ return new String(cleartextBytes, StandardCharsets.UTF_8);
+ } catch (AEADBadTagException e) {
+ throw new UncheckedIOException(new CryptoException("Authentication failed.", e));
+ }
+ }
+
+ private static class ThreadLocalSha256 extends ThreadLocal {
+
+ @Override
+ protected MessageDigest initialValue() {
+ try {
+ return MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError("SHA-256 exists in every JVM");
+ }
+ }
+
+ @Override
+ public MessageDigest get() {
+ final MessageDigest messageDigest = super.get();
+ messageDigest.reset();
+ return messageDigest;
+ }
+ }
+
+ /* ======================= destruction ======================= */
+
+ @Override
+ public void destroy() throws DestroyFailedException {
+ TheDestroyer.destroyQuietly(encryptionKey);
+ TheDestroyer.destroyQuietly(macKey);
+ }
+
+ @Override
+ public boolean isDestroyed() {
+ return encryptionKey.isDestroyed() && macKey.isDestroyed();
+ }
+
+}
diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/impl/TheDestroyer.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/impl/TheDestroyer.java
new file mode 100644
index 000000000..ae7f25071
--- /dev/null
+++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/impl/TheDestroyer.java
@@ -0,0 +1,20 @@
+package org.cryptomator.crypto.engine.impl;
+
+import javax.security.auth.DestroyFailedException;
+import javax.security.auth.Destroyable;
+
+final class TheDestroyer {
+
+ private TheDestroyer() {
+
+ }
+
+ public static void destroyQuietly(Destroyable d) {
+ try {
+ d.destroy();
+ } catch (DestroyFailedException e) {
+ // ignore
+ }
+ }
+
+}
diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/package-info.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/package-info.java
new file mode 100644
index 000000000..ed4065919
--- /dev/null
+++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/engine/package-info.java
@@ -0,0 +1,12 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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
+ *******************************************************************************/
+/**
+ * This is where the actual encryption, decryption, hashing and authenticating takes place.
+ */
+package org.cryptomator.crypto.engine;
\ No newline at end of file
diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/CryptoFile.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFile.java
similarity index 57%
rename from main/crypto-layer/src/main/java/org/cryptomator/crypto/CryptoFile.java
rename to main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFile.java
index 1a64d402c..769ed35ef 100644
--- a/main/crypto-layer/src/main/java/org/cryptomator/crypto/CryptoFile.java
+++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFile.java
@@ -1,4 +1,12 @@
-package org.cryptomator.crypto;
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.fs;
import java.io.IOException;
import java.io.UncheckedIOException;
@@ -6,21 +14,22 @@ import java.time.Instant;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import org.cryptomator.crypto.engine.Cryptor;
import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.ReadableFile;
import org.cryptomator.filesystem.WritableFile;
public class CryptoFile extends CryptoNode implements File {
- private static final String ENCRYPTED_FILE_EXT = ".file";
+ static final String FILE_EXT = ".file";
- public CryptoFile(CryptoFolder parent, String name) {
- super(parent, name);
+ public CryptoFile(CryptoFolder parent, String name, Cryptor cryptor) {
+ super(parent, name, cryptor);
}
@Override
String encryptedName() {
- return name() + ENCRYPTED_FILE_EXT;
+ return name() + FILE_EXT;
}
@Override
diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/CryptoFileSystem.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFileSystem.java
similarity index 54%
rename from main/crypto-layer/src/main/java/org/cryptomator/crypto/CryptoFileSystem.java
rename to main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFileSystem.java
index e03c72d48..59c7f2e4b 100644
--- a/main/crypto-layer/src/main/java/org/cryptomator/crypto/CryptoFileSystem.java
+++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFileSystem.java
@@ -1,12 +1,25 @@
-package org.cryptomator.crypto;
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.fs;
import java.io.IOException;
+import java.nio.ByteBuffer;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.cryptomator.crypto.engine.Cryptor;
import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.FileSystem;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.FolderCreateMode;
+import org.cryptomator.filesystem.WritableFile;
public class CryptoFileSystem extends CryptoFolder implements FileSystem {
@@ -18,8 +31,8 @@ public class CryptoFileSystem extends CryptoFolder implements FileSystem {
private final Folder physicalRoot;
- public CryptoFileSystem(Folder physicalRoot) {
- super(null, "");
+ public CryptoFileSystem(Folder physicalRoot, Cryptor cryptor) {
+ super(null, "", cryptor);
this.physicalRoot = physicalRoot;
}
@@ -28,12 +41,6 @@ public class CryptoFileSystem extends CryptoFolder implements FileSystem {
return physicalDataRoot().file(ROOT_DIR_FILE);
}
- @Override
- Folder physicalFolder() throws IOException {
- // TODO Auto-generated method stub
- return super.physicalFolder();
- }
-
@Override
Folder physicalDataRoot() {
return physicalRoot.folder(DATA_ROOT_DIR);
@@ -68,7 +75,15 @@ public class CryptoFileSystem extends CryptoFolder implements FileSystem {
public void create(FolderCreateMode mode) throws IOException {
physicalDataRoot().create(mode);
physicalMetadataRoot().create(mode);
- super.create(mode);
+ final File dirFile = physicalFile();
+ final String directoryId = getDirectoryId();
+ try (WritableFile writable = dirFile.openWritable(1, TimeUnit.SECONDS)) {
+ final ByteBuffer buf = ByteBuffer.wrap(directoryId.getBytes());
+ writable.write(buf);
+ } catch (TimeoutException e) {
+ throw new IOException("Failed to lock directory file in time." + dirFile, e);
+ }
+ physicalFolder().create(FolderCreateMode.INCLUDING_PARENTS);
}
}
diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/CryptoFolder.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFolder.java
similarity index 63%
rename from main/crypto-layer/src/main/java/org/cryptomator/crypto/CryptoFolder.java
rename to main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFolder.java
index d45109568..d5f5db866 100644
--- a/main/crypto-layer/src/main/java/org/cryptomator/crypto/CryptoFolder.java
+++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoFolder.java
@@ -1,5 +1,14 @@
-package org.cryptomator.crypto;
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.fs;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
@@ -14,6 +23,7 @@ import java.util.stream.Stream;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.lang3.StringUtils;
+import org.cryptomator.crypto.engine.Cryptor;
import org.cryptomator.filesystem.File;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.FolderCreateMode;
@@ -23,18 +33,17 @@ import org.cryptomator.filesystem.WritableFile;
class CryptoFolder extends CryptoNode implements Folder {
- private static final String ENCRYPTED_FILE_EXT = ".file";
- private static final String ENCRYPTED_DIR_EXT = ".dir";
+ static final String FILE_EXT = ".dir";
private final AtomicReference directoryId = new AtomicReference<>();
- public CryptoFolder(CryptoFolder parent, String name) {
- super(parent, name);
+ public CryptoFolder(CryptoFolder parent, String name, Cryptor cryptor) {
+ super(parent, name, cryptor);
}
@Override
String encryptedName() {
- return name() + ENCRYPTED_DIR_EXT;
+ return name() + FILE_EXT;
}
protected String getDirectoryId() throws IOException {
@@ -92,32 +101,32 @@ class CryptoFolder extends CryptoNode implements Folder {
@Override
public Stream files() throws IOException {
- return physicalFolder().files().map(File::name).filter(s -> s.endsWith(ENCRYPTED_FILE_EXT)).map(this::decryptFileName).map(this::file);
+ return physicalFolder().files().map(File::name).filter(s -> s.endsWith(CryptoFile.FILE_EXT)).map(this::decryptFileName).map(this::file);
}
private String decryptFileName(String encryptedFileName) {
- // TODO Auto-generated method stub
- return StringUtils.removeEnd(encryptedFileName, ENCRYPTED_FILE_EXT);
+ final String ciphertext = StringUtils.removeEnd(encryptedFileName, CryptoFile.FILE_EXT);
+ return cryptor.getFilenameCryptor().decryptFilename(ciphertext);
}
@Override
public CryptoFile file(String name) {
- return new CryptoFile(this, name);
+ return new CryptoFile(this, name, cryptor);
}
@Override
public Stream folders() throws IOException {
- return physicalFolder().files().map(File::name).filter(s -> s.endsWith(ENCRYPTED_DIR_EXT)).map(this::decryptFolderName).map(this::folder);
+ return physicalFolder().files().map(File::name).filter(s -> s.endsWith(CryptoFolder.FILE_EXT)).map(this::decryptFolderName).map(this::folder);
}
private String decryptFolderName(String encryptedFolderName) {
- // TODO Auto-generated method stub
- return StringUtils.removeEnd(encryptedFolderName, ENCRYPTED_DIR_EXT);
+ final String ciphertext = StringUtils.removeEnd(encryptedFolderName, CryptoFolder.FILE_EXT);
+ return cryptor.getFilenameCryptor().decryptFilename(ciphertext);
}
@Override
public CryptoFolder folder(String name) {
- return new CryptoFolder(this, name);
+ return new CryptoFolder(this, name, cryptor);
}
@Override
@@ -125,16 +134,21 @@ class CryptoFolder extends CryptoNode implements Folder {
final File dirFile = physicalFile();
if (dirFile.exists()) {
return;
- } else {
- final String directoryId = getDirectoryId();
- try (WritableFile writable = dirFile.openWritable(1, TimeUnit.SECONDS)) {
- final ByteBuffer buf = ByteBuffer.wrap(directoryId.getBytes());
- writable.write(buf);
- } catch (TimeoutException e) {
- throw new IOException("Failed to lock directory file in time." + dirFile, e);
- }
- physicalFolder().create(FolderCreateMode.INCLUDING_PARENTS);
}
+ if (!parent.exists() && FolderCreateMode.FAIL_IF_PARENT_IS_MISSING.equals(mode)) {
+ throw new FileNotFoundException(parent.name);
+ } else if (!parent.exists() && FolderCreateMode.INCLUDING_PARENTS.equals(mode)) {
+ parent.create(mode);
+ }
+ assert parent.exists();
+ final String directoryId = getDirectoryId();
+ try (WritableFile writable = dirFile.openWritable(1, TimeUnit.SECONDS)) {
+ final ByteBuffer buf = ByteBuffer.wrap(directoryId.getBytes());
+ writable.write(buf);
+ } catch (TimeoutException e) {
+ throw new IOException("Failed to lock directory file in time." + dirFile, e);
+ }
+ physicalFolder().create(FolderCreateMode.INCLUDING_PARENTS);
}
@Override
diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/CryptoNode.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoNode.java
similarity index 70%
rename from main/crypto-layer/src/main/java/org/cryptomator/crypto/CryptoNode.java
rename to main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoNode.java
index dc38a2a16..1fd6ca6ca 100644
--- a/main/crypto-layer/src/main/java/org/cryptomator/crypto/CryptoNode.java
+++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/CryptoNode.java
@@ -1,9 +1,18 @@
-package org.cryptomator.crypto;
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.fs;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Optional;
+import org.cryptomator.crypto.engine.Cryptor;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.Node;
@@ -11,10 +20,12 @@ abstract class CryptoNode implements Node {
protected final CryptoFolder parent;
protected final String name;
+ protected final Cryptor cryptor;
- public CryptoNode(CryptoFolder parent, String name) {
+ public CryptoNode(CryptoFolder parent, String name, Cryptor cryptor) {
this.parent = parent;
this.name = name;
+ this.cryptor = cryptor;
}
Folder physicalDataRoot() {
diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/package-info.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/package-info.java
new file mode 100644
index 000000000..e7027fbb1
--- /dev/null
+++ b/main/crypto-layer/src/main/java/org/cryptomator/crypto/fs/package-info.java
@@ -0,0 +1,14 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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
+ *******************************************************************************/
+/**
+ * Provides a decoration layer for the {@link org.cryptomator.filesystem Filesystem API}, consuming an encrypted file system and providing access to a cleartext filesystem.
+ * While the implementation in this package dictates the Vault directory layout, no encryption code can be found here.
+ * All cryptographic operations are delegated to the {@link org.cryptomator.crypto.engine CryptoEngine}.
+ */
+package org.cryptomator.crypto.fs;
\ No newline at end of file
diff --git a/main/crypto-layer/src/main/java/org/cryptomator/crypto/package-info.java b/main/crypto-layer/src/main/java/org/cryptomator/crypto/package-info.java
deleted file mode 100644
index 765e4fefa..000000000
--- a/main/crypto-layer/src/main/java/org/cryptomator/crypto/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * Provides a decoration layer for the {@link org.cryptomator.filesystem} API, consuming an encrypted file system and providing access to a cleartext filesystem.
- */
-package org.cryptomator.crypto;
\ No newline at end of file
diff --git a/main/crypto-layer/src/test/java/org/cryptomator/crypto/CryptoFileSystemTest.java b/main/crypto-layer/src/test/java/org/cryptomator/crypto/CryptoFileSystemTest.java
deleted file mode 100644
index 4f459df8f..000000000
--- a/main/crypto-layer/src/test/java/org/cryptomator/crypto/CryptoFileSystemTest.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package org.cryptomator.crypto;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-
-import org.cryptomator.filesystem.FileSystem;
-import org.cryptomator.filesystem.Folder;
-import org.cryptomator.filesystem.FolderCreateMode;
-import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class CryptoFileSystemTest {
-
- @Test
- public void testFilenameEncryption() throws UncheckedIOException, IOException {
- // some mock fs:
- FileSystem physicalFs = new InMemoryFileSystem();
- Folder dataRoot = physicalFs.folder("d");
- Assert.assertFalse(dataRoot.exists());
-
- // init crypto fs:
- FileSystem fs = new CryptoFileSystem(physicalFs);
- fs.create(FolderCreateMode.INCLUDING_PARENTS);
- Assert.assertTrue(dataRoot.exists());
- Assert.assertEquals(physicalFs.children().count(), 2);
- Assert.assertEquals(1, dataRoot.files().count()); // ROOT file
- Assert.assertEquals(1, dataRoot.folders().count()); // ROOT directory
-
- // add another encrypted folder:
- Folder testFolder = fs.folder("test");
- Assert.assertFalse(testFolder.exists());
- testFolder.create(FolderCreateMode.INCLUDING_PARENTS);
- Assert.assertTrue(testFolder.exists());
- Assert.assertEquals(2, dataRoot.folders().count());
- }
-
-}
diff --git a/main/crypto-layer/src/test/java/org/cryptomator/crypto/engine/NoCryptor.java b/main/crypto-layer/src/test/java/org/cryptomator/crypto/engine/NoCryptor.java
new file mode 100644
index 000000000..3bbc6ec88
--- /dev/null
+++ b/main/crypto-layer/src/test/java/org/cryptomator/crypto/engine/NoCryptor.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.engine;
+
+public class NoCryptor implements Cryptor {
+
+ private final FilenameCryptor filenameCryptor = new NoFilenameCryptor();
+
+ @Override
+ public FilenameCryptor getFilenameCryptor() {
+ return filenameCryptor;
+ }
+
+}
diff --git a/main/crypto-layer/src/test/java/org/cryptomator/crypto/engine/NoFilenameCryptor.java b/main/crypto-layer/src/test/java/org/cryptomator/crypto/engine/NoFilenameCryptor.java
new file mode 100644
index 000000000..2f5a8c482
--- /dev/null
+++ b/main/crypto-layer/src/test/java/org/cryptomator/crypto/engine/NoFilenameCryptor.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.engine;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import org.apache.commons.codec.binary.Base32;
+import org.apache.commons.codec.binary.BaseNCodec;
+
+class NoFilenameCryptor implements FilenameCryptor {
+
+ private static final BaseNCodec BASE32 = new Base32();
+ private static final ThreadLocal SHA1 = new ThreadLocalSha1();
+
+ @Override
+ public String hashDirectoryId(String cleartextDirectoryId) {
+ final byte[] cleartextBytes = cleartextDirectoryId.getBytes(StandardCharsets.UTF_8);
+ final byte[] hashedBytes = SHA1.get().digest(cleartextBytes);
+ return BASE32.encodeAsString(hashedBytes);
+ }
+
+ @Override
+ public String encryptFilename(String cleartextName) {
+ return cleartextName;
+ }
+
+ @Override
+ public String decryptFilename(String ciphertextName) {
+ return ciphertextName;
+ }
+
+ private static class ThreadLocalSha1 extends ThreadLocal {
+
+ @Override
+ protected MessageDigest initialValue() {
+ try {
+ return MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError("SHA-1 exists in every JVM");
+ }
+ }
+
+ @Override
+ public MessageDigest get() {
+ final MessageDigest sha1 = super.get();
+ sha1.reset();
+ return sha1;
+ }
+ }
+
+}
diff --git a/main/crypto-layer/src/test/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImplTest.java b/main/crypto-layer/src/test/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImplTest.java
new file mode 100644
index 000000000..bbead6366
--- /dev/null
+++ b/main/crypto-layer/src/test/java/org/cryptomator/crypto/engine/impl/FilenameCryptorImplTest.java
@@ -0,0 +1,57 @@
+package org.cryptomator.crypto.engine.impl;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.cryptomator.crypto.engine.Cryptor;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class FilenameCryptorImplTest {
+
+ @Test(timeout = 1000)
+ public void testDeterministicEncryptionOfFilenames() throws IOException {
+ final byte[] keyBytes = new byte[32];
+ final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES");
+ final SecretKey macKey = new SecretKeySpec(keyBytes, "AES");
+ final Cryptor cryptor = new CryptorImpl(encryptionKey, macKey);
+
+ // some random
+ for (int i = 0; i < 2000; i++) {
+ final String origName = UUID.randomUUID().toString();
+ final String encrypted1 = cryptor.getFilenameCryptor().encryptFilename(origName);
+ final String encrypted2 = cryptor.getFilenameCryptor().encryptFilename(origName);
+ Assert.assertEquals(encrypted1, encrypted2);
+ final String decrypted = cryptor.getFilenameCryptor().decryptFilename(encrypted1);
+ Assert.assertEquals(origName, decrypted);
+ }
+
+ // block size length file names
+ final String originalPath3 = "aaaabbbbccccdddd"; // 128 bit ascii
+ final String encryptedPath3a = cryptor.getFilenameCryptor().encryptFilename(originalPath3);
+ final String encryptedPath3b = cryptor.getFilenameCryptor().encryptFilename(originalPath3);
+ Assert.assertEquals(encryptedPath3a, encryptedPath3b);
+ final String decryptedPath3 = cryptor.getFilenameCryptor().decryptFilename(encryptedPath3a);
+ Assert.assertEquals(originalPath3, decryptedPath3);
+ }
+
+ @Test(timeout = 1000)
+ public void testDeterministicHashingOfDirectoryIds() throws IOException {
+ final byte[] keyBytes = new byte[32];
+ final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES");
+ final SecretKey macKey = new SecretKeySpec(keyBytes, "AES");
+ final Cryptor cryptor = new CryptorImpl(encryptionKey, macKey);
+
+ // some random
+ for (int i = 0; i < 2000; i++) {
+ final String originalDirectoryId = UUID.randomUUID().toString();
+ final String hashedDirectory1 = cryptor.getFilenameCryptor().hashDirectoryId(originalDirectoryId);
+ final String hashedDirectory2 = cryptor.getFilenameCryptor().hashDirectoryId(originalDirectoryId);
+ Assert.assertEquals(hashedDirectory1, hashedDirectory2);
+ }
+ }
+
+}
diff --git a/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/CryptoFileSystemTest.java b/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/CryptoFileSystemTest.java
new file mode 100644
index 000000000..2a805fb1b
--- /dev/null
+++ b/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/CryptoFileSystemTest.java
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.fs;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.cryptomator.crypto.engine.Cryptor;
+import org.cryptomator.crypto.engine.NoCryptor;
+import org.cryptomator.filesystem.FileSystem;
+import org.cryptomator.filesystem.Folder;
+import org.cryptomator.filesystem.FolderCreateMode;
+import org.cryptomator.filesystem.inmem.InMemoryFileSystem;
+import org.junit.Assert;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CryptoFileSystemTest {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CryptoFileSystemTest.class);
+
+ @Test
+ public void testFilenameEncryption() throws UncheckedIOException, IOException {
+ // mock cryptor:
+ Cryptor cryptor = new NoCryptor();
+
+ // some mock fs:
+ FileSystem physicalFs = new InMemoryFileSystem();
+ Folder physicalDataRoot = physicalFs.folder("d");
+ Assert.assertFalse(physicalDataRoot.exists());
+
+ // init crypto fs:
+ FileSystem fs = new CryptoFileSystem(physicalFs, cryptor);
+ fs.create(FolderCreateMode.INCLUDING_PARENTS);
+ Assert.assertTrue(physicalDataRoot.exists());
+ Assert.assertEquals(physicalFs.children().count(), 2);
+ Assert.assertEquals(1, physicalDataRoot.files().count()); // ROOT file
+ Assert.assertEquals(1, physicalDataRoot.folders().count()); // ROOT directory
+
+ // add another encrypted folder:
+ Folder fooFolder = fs.folder("foo");
+ Folder barFolder = fooFolder.folder("bar");
+ Assert.assertFalse(fooFolder.exists());
+ Assert.assertFalse(barFolder.exists());
+ barFolder.create(FolderCreateMode.INCLUDING_PARENTS);
+ Assert.assertTrue(fooFolder.exists());
+ Assert.assertTrue(barFolder.exists());
+ Assert.assertEquals(3, countDataFolders(physicalDataRoot)); // parent + foo + bar
+
+ LOG.info(DirectoryPrinter.print(fs));
+ LOG.info(DirectoryPrinter.print(physicalFs));
+ }
+
+ /**
+ * @return number of folders on second level inside the given dataRoot folder.
+ */
+ private static int countDataFolders(Folder dataRoot) {
+ final AtomicInteger num = new AtomicInteger();
+ DirectoryWalker.walk(dataRoot, 0, 2, (node) -> {
+ if (node instanceof Folder) {
+ final Folder nodeParent = node.parent().get();
+ final Folder nodeParentParent = nodeParent.parent().orElse(null);
+ if (nodeParentParent != null && nodeParentParent.equals(dataRoot)) {
+ num.incrementAndGet();
+ }
+ }
+ });
+ return num.get();
+ }
+
+}
diff --git a/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/DirectoryPrinter.java b/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/DirectoryPrinter.java
new file mode 100644
index 000000000..85859b1eb
--- /dev/null
+++ b/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/DirectoryPrinter.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.fs;
+
+import java.util.Optional;
+
+import org.cryptomator.filesystem.File;
+import org.cryptomator.filesystem.Folder;
+
+public final class DirectoryPrinter {
+
+ private DirectoryPrinter() {
+ }
+
+ public static String print(Folder folder) {
+ StringBuilder sb = new StringBuilder(folder.name()).append('\n');
+
+ DirectoryWalker.walk(folder, (node) -> {
+ Optional extends Folder> parent = node.parent();
+ while (parent.isPresent()) {
+ sb.append(" ");
+ parent = parent.get().parent();
+ }
+ if (node instanceof Folder) {
+ sb.append(node.name()).append('/').append('\n');
+ } else if (node instanceof File) {
+ sb.append(node.name()).append('\n');
+ }
+ });
+
+ return sb.toString();
+ }
+
+}
diff --git a/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/DirectoryWalker.java b/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/DirectoryWalker.java
new file mode 100644
index 000000000..497468718
--- /dev/null
+++ b/main/crypto-layer/src/test/java/org/cryptomator/crypto/fs/DirectoryWalker.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.fs;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.util.function.Consumer;
+
+import org.cryptomator.filesystem.Folder;
+import org.cryptomator.filesystem.Node;
+
+final class DirectoryWalker {
+
+ private DirectoryWalker() {
+ }
+
+ public static void walk(Folder folder, Consumer visitor) {
+ walk(folder, 0, Integer.MAX_VALUE, visitor);
+ }
+
+ public static void walk(Folder folder, int depth, int maxDepth, Consumer visitor) {
+ try {
+ folder.files().forEach(visitor);
+ if (depth == maxDepth) {
+ return;
+ } else {
+ folder.folders().forEach(childFolder -> {
+ visitor.accept(childFolder);
+ walk(childFolder, depth + 1, maxDepth, visitor);
+ });
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+}
diff --git a/main/crypto-layer/src/test/resources/log4j2.xml b/main/crypto-layer/src/test/resources/log4j2.xml
new file mode 100644
index 000000000..9b4889392
--- /dev/null
+++ b/main/crypto-layer/src/test/resources/log4j2.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/main/filesystem-inmemory/.gitignore b/main/filesystem-inmemory/.gitignore
new file mode 100644
index 000000000..b83d22266
--- /dev/null
+++ b/main/filesystem-inmemory/.gitignore
@@ -0,0 +1 @@
+/target/
diff --git a/main/filesystem-inmemory/pom.xml b/main/filesystem-inmemory/pom.xml
new file mode 100644
index 000000000..eca6b433f
--- /dev/null
+++ b/main/filesystem-inmemory/pom.xml
@@ -0,0 +1,26 @@
+
+
+
+ 4.0.0
+
+ org.cryptomator
+ main
+ 0.11.0-SNAPSHOT
+
+ filesystem-inmemory
+ Cryptomator in-memory filesystem
+
+
+
+ org.cryptomator
+ filesystem-api
+
+
+
\ No newline at end of file
diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java
similarity index 86%
rename from main/filesystem-api/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java
rename to main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java
index a6cc92070..90963682a 100644
--- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java
+++ b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFile.java
@@ -1,3 +1,11 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.filesystem.inmem;
import java.io.FileNotFoundException;
@@ -12,12 +20,12 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.cryptomator.filesystem.ReadableFile;
import org.cryptomator.filesystem.WritableFile;
-public class InMemoryFile extends InMemoryNode implements ReadableFile, WritableFile {
+class InMemoryFile extends InMemoryNode implements ReadableFile, WritableFile {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private ByteBuffer content = ByteBuffer.wrap(new byte[0]);
- InMemoryFile(InMemoryFolder parent, String name, Instant lastModified) {
+ public InMemoryFile(InMemoryFolder parent, String name, Instant lastModified) {
super(parent, name, lastModified);
}
diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFileSystem.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFileSystem.java
similarity index 57%
rename from main/filesystem-api/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFileSystem.java
rename to main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFileSystem.java
index 86f403a86..bc1b8ce3f 100644
--- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFileSystem.java
+++ b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFileSystem.java
@@ -1,3 +1,11 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.filesystem.inmem;
import java.time.Instant;
diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java
similarity index 82%
rename from main/filesystem-api/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java
rename to main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java
index 6bb1cbbec..114b667f2 100644
--- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java
+++ b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryFolder.java
@@ -1,3 +1,11 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.filesystem.inmem;
import java.io.FileNotFoundException;
@@ -14,12 +22,12 @@ import org.apache.commons.io.FileExistsException;
import org.cryptomator.filesystem.Folder;
import org.cryptomator.filesystem.FolderCreateMode;
-public class InMemoryFolder extends InMemoryNode implements Folder {
+class InMemoryFolder extends InMemoryNode implements Folder {
final Map children = new TreeMap<>();
final Map volatileChildren = new HashMap<>();
- InMemoryFolder(InMemoryFolder parent, String name, Instant lastModified) {
+ public InMemoryFolder(InMemoryFolder parent, String name, Instant lastModified) {
super(parent, name, lastModified);
}
diff --git a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/inmem/InMemoryNode.java b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryNode.java
similarity index 71%
rename from main/filesystem-api/src/main/java/org/cryptomator/filesystem/inmem/InMemoryNode.java
rename to main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryNode.java
index 53dd7d833..fbe899529 100644
--- a/main/filesystem-api/src/main/java/org/cryptomator/filesystem/inmem/InMemoryNode.java
+++ b/main/filesystem-inmemory/src/main/java/org/cryptomator/filesystem/inmem/InMemoryNode.java
@@ -1,3 +1,11 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.filesystem.inmem;
import java.time.Instant;
@@ -5,13 +13,13 @@ import java.util.Optional;
import org.cryptomator.filesystem.Node;
-public class InMemoryNode implements Node {
+class InMemoryNode implements Node {
protected final InMemoryFolder parent;
protected final String name;
protected Instant lastModified;
- InMemoryNode(InMemoryFolder parent, String name, Instant lastModified) {
+ public InMemoryNode(InMemoryFolder parent, String name, Instant lastModified) {
this.parent = parent;
this.name = name;
this.lastModified = lastModified;
diff --git a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java b/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java
similarity index 88%
rename from main/filesystem-api/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java
rename to main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java
index 3b04ac297..563a64180 100644
--- a/main/filesystem-api/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java
+++ b/main/filesystem-inmemory/src/test/java/org/cryptomator/filesystem/inmem/InMemoryFileSystemTest.java
@@ -1,3 +1,11 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Sebastian Stenzel and others.
+ * 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.filesystem.inmem;
import java.io.IOException;
diff --git a/main/pom.xml b/main/pom.xml
index 54732d1a9..c97d31d9c 100644
--- a/main/pom.xml
+++ b/main/pom.xml
@@ -56,12 +56,23 @@
org.cryptomator
- core
+ filesystem-api
${project.version}
org.cryptomator
- filesystem-api
+ filesystem-inmemory
+ ${project.version}
+
+
+ org.cryptomator
+ crypto-layer
+ ${project.version}
+
+
+
+ org.cryptomator
+ core
${project.version}
@@ -198,11 +209,12 @@
filesystem-api
+ filesystem-inmemory
+ crypto-layer
crypto-api
crypto-aes
core
ui
- crypto-layer