Skip to content

Commit

Permalink
Merge pull request #99 from cryptomator/feature/#98-change-vault-init
Browse files Browse the repository at this point in the history
Feature/#98 change vault init
  • Loading branch information
Armin Schrenk authored Mar 16, 2021
2 parents c6c0b73 + 5237e83 commit d5772f1
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 132 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.cryptomator.cryptofs;

import java.nio.file.NoSuchFileException;

public class ContentRootMissingException extends NoSuchFileException {

public ContentRootMissingException(String msg) {
super(msg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public CryptoFileSystemImpl(CryptoFileSystemProvider provider, CryptoFileSystems
PathMatcherFactory pathMatcherFactory, DirectoryStreamFactory directoryStreamFactory, DirectoryIdProvider dirIdProvider,
AttributeProvider fileAttributeProvider, AttributeByNameProvider fileAttributeByNameProvider, AttributeViewProvider fileAttributeViewProvider,
OpenCryptoFiles openCryptoFiles, Symlinks symlinks, FinallyUtil finallyUtil, CiphertextDirectoryDeleter ciphertextDirDeleter, ReadonlyFlag readonlyFlag,
CryptoFileSystemProperties fileSystemProperties, RootDirectoryInitializer rootDirectoryInitializer) {
CryptoFileSystemProperties fileSystemProperties) {
this.provider = provider;
this.cryptoFileSystems = cryptoFileSystems;
this.pathToVault = pathToVault;
Expand All @@ -129,8 +129,6 @@ public CryptoFileSystemImpl(CryptoFileSystemProvider provider, CryptoFileSystems

this.rootPath = cryptoPathFactory.rootFor(this);
this.emptyPath = cryptoPathFactory.emptyFor(this);

rootDirectoryInitializer.initialize(rootPath);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.cryptomator.cryptofs.ch.AsyncDelegatingFileChannel;
import org.cryptomator.cryptofs.common.Constants;
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;

Expand Down Expand Up @@ -140,16 +141,18 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope
throw new NotDirectoryException(pathToVault.toString());
}
byte[] rawKey = new byte[0];
try (Masterkey key = properties.keyLoader(keyId.getScheme()).loadKey(keyId)) {
var config = VaultConfig.createNew().cipherCombo(properties.cipherCombo()).maxFilenameLength(properties.maxNameLength()).build();
try (Masterkey key = properties.keyLoader(keyId.getScheme()).loadKey(keyId);
Cryptor cryptor = config.getCipherCombo().getCryptorProvider(strongSecureRandom()).withKey(key)) {
rawKey = key.getEncoded();
// save vault config:
Path vaultConfigPath = pathToVault.resolve(properties.vaultConfigFilename());
var config = VaultConfig.createNew().cipherCombo(properties.cipherCombo()).maxFilenameLength(properties.maxNameLength()).build();
var token = config.toToken(keyId.toString(), rawKey);
Files.writeString(vaultConfigPath, token, StandardCharsets.US_ASCII, WRITE, CREATE_NEW);
// create "d" dir:
Path dataDirPath = pathToVault.resolve(Constants.DATA_DIR_NAME);
Files.createDirectories(dataDirPath);
// create "d" dir and root:
String dirHash = cryptor.fileNameCryptor().hashDirectoryId(Constants.ROOT_DIR_ID);
Path vaultCipherRootPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2));
Files.createDirectories(vaultCipherRootPath);
} finally {
Arrays.fill(rawKey, (byte) 0x00);
}
Expand Down
43 changes: 32 additions & 11 deletions src/main/java/org/cryptomator/cryptofs/CryptoFileSystems.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,42 @@ public CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathT
rawKey = key.getEncoded();
var config = configLoader.verify(rawKey, Constants.VAULT_VERSION);
var adjustedProperties = adjustForCapabilities(pathToVault, properties);
return fileSystems.compute(normalizedPathToVault, (path, fs) -> {
if (fs == null) {
return create(provider, normalizedPathToVault, adjustedProperties, key, config);
} else {
throw new FileSystemAlreadyExistsException();
}
});
var keyCopy = Masterkey.createFromRaw(key.getEncoded()); // TODO replace with key.clone() eventually
var cryptor = config.getCipherCombo().getCryptorProvider(csprng).withKey(keyCopy);
try {
checkVaultRootExistence(pathToVault, cryptor);
return fileSystems.compute(normalizedPathToVault, (path, fs) -> {
if (fs == null) {
return create(provider, normalizedPathToVault, adjustedProperties, cryptor, config);
} else {
throw new FileSystemAlreadyExistsException();
}
});
} catch (Exception e) { //on any exception, destroy the cryptor
cryptor.destroy();
throw e;
}
} finally {
Arrays.fill(rawKey, (byte) 0x00);
}
}

/**
* Checks if the vault has a content root folder. If not, an exception is raised.
* @param pathToVault Path to the vault root
* @param cryptor Cryptor object initialized with the correct masterkey
* @throws ContentRootMissingException If the existence of encrypted vault content root cannot be ensured
*/
private void checkVaultRootExistence(Path pathToVault, Cryptor cryptor) throws ContentRootMissingException {
String dirHash = cryptor.fileNameCryptor().hashDirectoryId(Constants.ROOT_DIR_ID);
Path vaultCipherRootPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(dirHash.substring(0, 2)).resolve(dirHash.substring(2));
if (!Files.exists(vaultCipherRootPath)) {
throw new ContentRootMissingException("The encrypted root directory of the vault " + pathToVault + " is missing.");
}
}

// synchronized access to non-threadsafe cryptoFileSystemComponentBuilder required
private synchronized CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathToVault, CryptoFileSystemProperties properties, Masterkey masterkey, VaultConfig config) {
Cryptor cryptor = config.getCipherCombo().getCryptorProvider(csprng).withKey(masterkey);
private synchronized CryptoFileSystemImpl create(CryptoFileSystemProvider provider, Path pathToVault, CryptoFileSystemProperties properties, Cryptor cryptor, VaultConfig config) {
return cryptoFileSystemComponentBuilder //
.cryptor(cryptor) //
.vaultConfig(config) //
Expand All @@ -83,9 +104,9 @@ private synchronized CryptoFileSystemImpl create(CryptoFileSystemProvider provid
* Attempts to read a vault config file
*
* @param pathToVault path to the vault's root
* @param properties properties used when attempting to construct a fs for this vault
* @param properties properties used when attempting to construct a fs for this vault
* @return The contents of the file decoded in ASCII
* @throws IOException If the file could not be read
* @throws IOException If the file could not be read
* @throws FileSystemNeedsMigrationException If the file doesn't exists, but a legacy masterkey file was found instead
*/
private String readVaultConfigFile(Path pathToVault, CryptoFileSystemProperties properties) throws IOException, FileSystemNeedsMigrationException {
Expand Down
26 changes: 0 additions & 26 deletions src/main/java/org/cryptomator/cryptofs/FilesWrapper.java

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ public class CryptoFileSystemImplTest {
private final PathMatcherFactory pathMatcherFactory = mock(PathMatcherFactory.class);
private final CryptoPathFactory cryptoPathFactory = mock(CryptoPathFactory.class);
private final CryptoFileSystemStats stats = mock(CryptoFileSystemStats.class);
private final RootDirectoryInitializer rootDirectoryInitializer = mock(RootDirectoryInitializer.class);
private final DirectoryStreamFactory directoryStreamFactory = mock(DirectoryStreamFactory.class);
private final FinallyUtil finallyUtil = mock(FinallyUtil.class);
private final CiphertextDirectoryDeleter ciphertextDirDeleter = mock(CiphertextDirectoryDeleter.class);
Expand Down Expand Up @@ -124,7 +123,7 @@ public void setup() {
pathMatcherFactory, directoryStreamFactory, dirIdProvider,
fileAttributeProvider, fileAttributeByNameProvider, fileAttributeViewProvider,
openCryptoFiles, symlinks, finallyUtil, ciphertextDirDeleter, readonlyFlag,
fileSystemProperties, rootDirectoryInitializer);
fileSystemProperties);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.nio.file.spi.FileSystemProvider;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.stream.Stream;
Expand Down Expand Up @@ -187,6 +188,14 @@ public void testInitialize() throws IOException, MasterkeyLoadingFailedException

Assertions.assertTrue(Files.isDirectory(dataDir));
Assertions.assertTrue(Files.isRegularFile(vaultConfigFile));

Optional<Path> preRootDir = Files.list(dataDir).findFirst();
Assertions.assertTrue(preRootDir.isPresent());
Assertions.assertTrue(Files.isDirectory(preRootDir.get()));

Optional<Path> rootDir = Files.list(preRootDir.get()).findFirst();
Assertions.assertTrue(rootDir.isPresent());
Assertions.assertTrue(Files.isDirectory(rootDir.get()));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.cryptomator.cryptofs.common.FileSystemCapabilityChecker;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.FileNameCryptor;
import org.cryptomator.cryptolib.api.Masterkey;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
import org.cryptomator.cryptolib.api.MasterkeyLoadingFailedException;
Expand Down Expand Up @@ -35,6 +36,9 @@ public class CryptoFileSystemsTest {
private final Path pathToVault = mock(Path.class, "vaultPath");
private final Path normalizedPathToVault = mock(Path.class, "normalizedVaultPath");
private final Path configFilePath = mock(Path.class, "normalizedVaultPath/vault.cryptomator");
private final Path dataDirPath = mock(Path.class, "normalizedVaultPath/d");
private final Path preContenRootPath = mock(Path.class, "normalizedVaultPath/d/AB");
private final Path contenRootPath = mock(Path.class, "normalizedVaultPath/d/AB/CDEFGHIJKLMNOP");
private final FileSystemCapabilityChecker capabilityChecker = mock(FileSystemCapabilityChecker.class);
private final CryptoFileSystemProvider provider = mock(CryptoFileSystemProvider.class);
private final CryptoFileSystemProperties properties = mock(CryptoFileSystemProperties.class);
Expand All @@ -43,24 +47,28 @@ public class CryptoFileSystemsTest {
private final VaultConfig.UnverifiedVaultConfig configLoader = mock(VaultConfig.UnverifiedVaultConfig.class);
private final MasterkeyLoader keyLoader = mock(MasterkeyLoader.class);
private final Masterkey masterkey = mock(Masterkey.class);
private final Masterkey clonedMasterkey = Mockito.mock(Masterkey.class);
private final byte[] rawKey = new byte[64];
private final VaultConfig vaultConfig = mock(VaultConfig.class);
private final VaultCipherCombo cipherCombo = mock(VaultCipherCombo.class);
private final SecureRandom csprng = Mockito.mock(SecureRandom.class);
private final CryptorProvider cryptorProvider = mock(CryptorProvider.class);
private final Cryptor cryptor = mock(Cryptor.class);
private final FileNameCryptor fileNameCryptor = mock(FileNameCryptor.class);
private final CryptoFileSystemComponent.Builder cryptoFileSystemComponentBuilder = mock(CryptoFileSystemComponent.Builder.class);


private MockedStatic<VaultConfig> vaultConficClass;
private MockedStatic<Files> filesClass;
private MockedStatic<Masterkey> masterkeyClass;

private final CryptoFileSystems inTest = new CryptoFileSystems(cryptoFileSystemComponentBuilder, capabilityChecker, csprng);

@BeforeEach
public void setup() throws IOException, MasterkeyLoadingFailedException {
filesClass = Mockito.mockStatic(Files.class);
vaultConficClass = Mockito.mockStatic(VaultConfig.class);
filesClass = Mockito.mockStatic(Files.class);
masterkeyClass = Mockito.mockStatic(Masterkey.class);

when(pathToVault.normalize()).thenReturn(normalizedPathToVault);
when(normalizedPathToVault.resolve("vault.cryptomator")).thenReturn(configFilePath);
Expand All @@ -73,9 +81,17 @@ public void setup() throws IOException, MasterkeyLoadingFailedException {
when(keyLoader.loadKey(Mockito.any())).thenReturn(masterkey);
when(masterkey.getEncoded()).thenReturn(rawKey);
when(configLoader.verify(rawKey, Constants.VAULT_VERSION)).thenReturn(vaultConfig);
masterkeyClass.when(() -> Masterkey.createFromRaw(rawKey)).thenReturn(clonedMasterkey);
when(cryptorProvider.withKey(clonedMasterkey)).thenReturn(cryptor);
when(vaultConfig.getCipherCombo()).thenReturn(cipherCombo);
when(cipherCombo.getCryptorProvider(csprng)).thenReturn(cryptorProvider);
when(cryptorProvider.withKey(masterkey)).thenReturn(cryptor);
when(cryptor.fileNameCryptor()).thenReturn(fileNameCryptor);
when(fileNameCryptor.hashDirectoryId("")).thenReturn("ABCDEFGHIJKLMNOP");
when(pathToVault.resolve(Constants.DATA_DIR_NAME)).thenReturn(dataDirPath);
when(dataDirPath.resolve("AB")).thenReturn(preContenRootPath);
when(preContenRootPath.resolve("CDEFGHIJKLMNOP")).thenReturn(contenRootPath);
filesClass.when(() -> Files.exists(contenRootPath)).thenReturn(true);
when(cryptoFileSystemComponentBuilder.cryptor(any())).thenReturn(cryptoFileSystemComponentBuilder);
when(cryptoFileSystemComponentBuilder.vaultConfig(any())).thenReturn(cryptoFileSystemComponentBuilder);
when(cryptoFileSystemComponentBuilder.pathToVault(any())).thenReturn(cryptoFileSystemComponentBuilder);
Expand All @@ -89,6 +105,7 @@ public void setup() throws IOException, MasterkeyLoadingFailedException {
public void tearDown() {
vaultConficClass.close();
filesClass.close();
masterkeyClass.close();
}

@Test
Expand Down Expand Up @@ -130,6 +147,13 @@ public void testCreateDoesNotThrowFileSystemAlreadyExistsExceptionIfFileSystemIs
Assertions.assertTrue(inTest.contains(fileSystem2));
}

@Test
public void testCreateThrowsIOExceptionIfContentRootExistenceCheckFails() {
filesClass.when(() -> Files.exists(contenRootPath)).thenReturn(false);

Assertions.assertThrows(IOException.class, () -> inTest.create(provider, pathToVault, properties));
}

@Test
public void testGetReturnsFileSystemForPathIfItExists() throws IOException, MasterkeyLoadingFailedException {
CryptoFileSystemImpl fileSystem = inTest.create(provider, pathToVault, properties);
Expand Down

This file was deleted.

0 comments on commit d5772f1

Please sign in to comment.