Skip to content

Commit

Permalink
Merge branch 'release/1.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
overheadhunter committed Jun 1, 2017
2 parents 8aaf40a + a00cefd commit 480d27f
Show file tree
Hide file tree
Showing 21 changed files with 319 additions and 174 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ or to use one of the standard methods from ``FileSystems#newFileSystem``:

```java
Path storageLocation = Paths.get("/home/cryptobot/vault");
URI uri = CryptoFileSystemUris.createUri(storageLocation);
URI uri = CryptoFileSystemUri.create(storageLocation);
FileSystem fileSystem = FileSystems.newFileSystem(
uri,
CryptoFileSystemProperties.cryptoFileSystemProperties()
Expand Down
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.cryptomator</groupId>
<artifactId>cryptofs</artifactId>
<version>1.2.3</version>
<version>1.3.0</version>
<name>Cryptomator Crypto Filesystem</name>
<description>This library provides the Java filesystem provider used by Cryptomator.</description>
<url>https://github.com/cryptomator/cryptofs</url>
Expand All @@ -15,9 +15,9 @@

<properties>
<java.version>1.8</java.version>
<cryptolib.version>1.1.1</cryptolib.version>
<cryptolib.version>1.1.2</cryptolib.version>
<dagger.version>2.11</dagger.version>
<guava.version>21.0</guava.version>
<guava.version>22.0</guava.version>
<commons.lang.version>3.5</commons.lang.version>
<slf4j.version>1.7.25</slf4j.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/cryptomator/cryptofs/ChunkCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public ChunkCache(ChunkLoader chunkLoader, ChunkSaver chunkSaver, CryptoFileSyst
.removalListener(removal -> chunkSaver.save((Long) removal.getKey(), (ChunkData) removal.getValue())) //
.build(new CacheLoader<Long, ChunkData>() {
@Override
public ChunkData load(Long key) throws Exception {
public ChunkData load(Long key) throws IOException {
return chunkLoader.load(key);
}
});
Expand All @@ -39,6 +39,7 @@ public ChunkData get(long chunkIndex) throws IOException {
stats.addChunkCacheAccess();
return chunks.get(chunkIndex);
} catch (ExecutionException e) {
assert e.getCause() != null; // no exception in ChunkLoader -> no executionException during chunk loading ;-)
throw (IOException) e.getCause();
} catch (UncheckedExecutionException e) {
if (e.getCause() instanceof AuthenticationFailedException) {
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/cryptomator/cryptofs/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ final class Constants {
public static final String DIR_PREFIX = "0";
public static final int NAME_SHORTENING_THRESHOLD = 129;
public static final int VAULT_VERSION = 5;
public static final String ROOT_DIR_ID = "";

public static final String SEPARATOR = "/";

Expand Down
33 changes: 16 additions & 17 deletions src/main/java/org/cryptomator/cryptofs/CryptoFileSystemImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.UserPrincipalLookupService;
import java.nio.file.spi.FileSystemProvider;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Map;
Expand Down Expand Up @@ -263,23 +264,8 @@ <V extends FileAttributeView> V getFileAttributeView(CryptoPath cleartextPath, C
void checkAccess(CryptoPath cleartextPath, AccessMode... modes) throws IOException {
if (fileStore.supportsFileAttributeView(PosixFileAttributeView.class)) {
Set<PosixFilePermission> permissions = readAttributes(cleartextPath, PosixFileAttributes.class).permissions();
boolean accessGranted = true;
for (AccessMode accessMode : modes) {
switch (accessMode) {
case READ:
accessGranted &= permissions.contains(PosixFilePermission.OWNER_READ);
break;
case WRITE:
accessGranted &= permissions.contains(PosixFilePermission.OWNER_WRITE);
break;
case EXECUTE:
accessGranted &= permissions.contains(PosixFilePermission.OWNER_EXECUTE);
break;
default:
throw new UnsupportedOperationException("AccessMode " + accessMode + " not supported.");
}
}
if (!accessGranted) {
boolean accessDenied = Arrays.stream(modes).anyMatch(accessMode -> !hasAccess(permissions, accessMode));
if (accessDenied) {
throw new AccessDeniedException(cleartextPath.toString());
}
} else if (fileStore.supportsFileAttributeView(DosFileAttributeView.class)) {
Expand All @@ -293,6 +279,19 @@ void checkAccess(CryptoPath cleartextPath, AccessMode... modes) throws IOExcepti
}
}

private boolean hasAccess(Set<PosixFilePermission> permissions, AccessMode accessMode) {
switch (accessMode) {
case READ:
return permissions.contains(PosixFilePermission.OWNER_READ);
case WRITE:
return permissions.contains(PosixFilePermission.OWNER_WRITE);
case EXECUTE:
return permissions.contains(PosixFilePermission.OWNER_EXECUTE);
default:
throw new UnsupportedOperationException("AccessMode " + accessMode + " not supported.");
}
}

boolean isHidden(CryptoPath cleartextPath) throws IOException {
DosFileAttributeView view = this.getFileAttributeView(cleartextPath, DosFileAttributeView.class);
if (view != null) {
Expand Down
17 changes: 4 additions & 13 deletions src/main/java/org/cryptomator/cryptofs/CryptoFileSystemModule.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package org.cryptomator.cryptofs;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static java.nio.file.StandardOpenOption.CREATE_NEW;
import static java.nio.file.StandardOpenOption.WRITE;
import static org.cryptomator.cryptofs.UncheckedThrows.rethrowUnchecked;

import java.io.IOException;
Expand Down Expand Up @@ -33,17 +31,10 @@ public Cryptor provideCryptor(CryptorProvider cryptorProvider, @PathToVault Path
return rethrowUnchecked(IOException.class).from(() -> {
Path masterKeyPath = pathToVault.resolve(properties.masterkeyFilename());
Path backupKeyPath = pathToVault.resolve(properties.masterkeyFilename() + Constants.MASTERKEY_BACKUP_SUFFIX);
Cryptor cryptor;
if (Files.isRegularFile(masterKeyPath)) {
byte[] keyFileContents = Files.readAllBytes(masterKeyPath);
cryptor = cryptorProvider.createFromKeyFile(KeyFile.parse(keyFileContents), properties.passphrase(), Constants.VAULT_VERSION);
Files.copy(masterKeyPath, backupKeyPath, REPLACE_EXISTING);
} else {
cryptor = cryptorProvider.createNew();
byte[] keyFileContents = cryptor.writeKeysToMasterkeyFile(properties.passphrase(), Constants.VAULT_VERSION).serialize();
Files.createDirectories(pathToVault);
Files.write(masterKeyPath, keyFileContents, CREATE_NEW, WRITE);
}
assert Files.exists(masterKeyPath); // since 1.3.0 a file system can only be created for existing vaults. initialization is done before.
byte[] keyFileContents = Files.readAllBytes(masterKeyPath);
Cryptor cryptor = cryptorProvider.createFromKeyFile(KeyFile.parse(keyFileContents), properties.passphrase(), Constants.VAULT_VERSION);
Files.copy(masterKeyPath, backupKeyPath, REPLACE_EXISTING);
return cryptor;
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
*******************************************************************************/
package org.cryptomator.cryptofs;

import static java.lang.Boolean.TRUE;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableSet;

import java.net.URI;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.AbstractMap;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
Expand All @@ -39,11 +39,6 @@ public class CryptoFileSystemProperties extends AbstractMap<String, Object> {
*/
public static final String PROPERTY_PASSPHRASE = "passphrase";

/**
* Key identifying the readonly property (<code>true</code> or <code>false</code>) for a vault.
*/
public static final String PROPERTY_READONLY = "readonly";

/**
* Key identifying the name of the masterkey file located inside the vault directory.
*
Expand All @@ -53,12 +48,35 @@ public class CryptoFileSystemProperties extends AbstractMap<String, Object> {

static final String DEFAULT_MASTERKEY_FILENAME = "masterkey.cryptomator";

/**
* Key identifying the filesystem flags.
*
* @since 1.3.0
*/
public static final String PROPERTY_FILESYSTEM_FLAGS = "flags";

static final Set<FileSystemFlags> DEFAULT_FILESYSTEM_FLAGS = unmodifiableSet(EnumSet.of(FileSystemFlags.INIT_IMPLICITLY));

public enum FileSystemFlags {
/**
* If present, the vault is opened in read-only mode.
*/
READONLY,

/**
* If present, the vault structure will implicitly get initialized upon filesystem creation.
*
* @deprecated Will get removed in version 2.0.0. Use {@link CryptoFileSystemProvider#initialize(Path, String, CharSequence)} explicitly.
*/
@Deprecated INIT_IMPLICITLY
};

private final Set<Entry<String, Object>> entries;

private CryptoFileSystemProperties(Builder builder) {
this.entries = unmodifiableSet(new HashSet<>(asList( //
entry(PROPERTY_PASSPHRASE, builder.passphrase), //
entry(PROPERTY_READONLY, builder.readonly), //
entry(PROPERTY_FILESYSTEM_FLAGS, builder.flags), //
entry(PROPERTY_MASTERKEY_FILENAME, builder.masterkeyFilename) //
)));
}
Expand All @@ -67,8 +85,17 @@ CharSequence passphrase() {
return (CharSequence) get(PROPERTY_PASSPHRASE);
}

@SuppressWarnings("unchecked")
Set<FileSystemFlags> flags() {
return (Set<FileSystemFlags>) get(PROPERTY_FILESYSTEM_FLAGS);
}

boolean readonly() {
return (boolean) get(PROPERTY_READONLY);
return flags().contains(FileSystemFlags.READONLY);
}

boolean initializeImplicitly() {
return flags().contains(FileSystemFlags.INIT_IMPLICITLY);
}

String masterkeyFilename() {
Expand Down Expand Up @@ -143,7 +170,7 @@ public static CryptoFileSystemProperties wrap(Map<String, ?> properties) {
public static class Builder {

private CharSequence passphrase;
private boolean readonly = false;
private final Set<FileSystemFlags> flags = EnumSet.copyOf(DEFAULT_FILESYSTEM_FLAGS);
private String masterkeyFilename = DEFAULT_MASTERKEY_FILENAME;

private Builder() {
Expand All @@ -152,12 +179,7 @@ private Builder() {
private Builder(Map<String, ?> properties) {
checkedSet(CharSequence.class, PROPERTY_PASSPHRASE, properties, this::withPassphrase);
checkedSet(String.class, PROPERTY_MASTERKEY_FILENAME, properties, this::withMasterkeyFilename);
checkedSet(Boolean.class, PROPERTY_READONLY, properties, readonly -> {
if (TRUE.equals(readonly)) {
withReadonlyFlag();
}
});

checkedSet(Set.class, PROPERTY_FILESYSTEM_FLAGS, properties, this::withFlags);
}

private <T> void checkedSet(Class<T> type, String key, Map<String, ?> properties, Consumer<T> setter) {
Expand All @@ -182,13 +204,19 @@ public Builder withPassphrase(CharSequence passphrase) {
return this;
}

public Builder withFlags(Set<FileSystemFlags> flags) {
this.flags.clear();
this.flags.addAll(flags);
return this;
}

/**
* Sets the readonly flag for a CryptoFileSystem.
*
* @return this
*/
public Builder withReadonlyFlag() {
this.readonly = true;
flags.add(FileSystemFlags.READONLY);
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static java.nio.file.StandardOpenOption.CREATE_NEW;
import static java.nio.file.StandardOpenOption.WRITE;
import static org.cryptomator.cryptofs.CryptoFileSystemUris.createUri;
import static org.cryptomator.cryptofs.CryptoFileSystemUri.create;

import java.io.IOException;
import java.net.URI;
Expand All @@ -28,6 +28,7 @@
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NotDirectoryException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.ProviderMismatchException;
Expand All @@ -42,8 +43,8 @@
import java.util.Set;
import java.util.concurrent.ExecutorService;

import org.cryptomator.cryptofs.CryptoFileSystemUris.ParsedUri;
import org.cryptomator.cryptolib.Cryptors;
import org.cryptomator.cryptolib.api.Cryptor;
import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.InvalidPassphraseException;

Expand Down Expand Up @@ -73,9 +74,9 @@
* Afterwards you can use the created {@code FileSystem} to create paths, do directory listings, create files and so on.
*
* <p>
* To create a new FileSystem from a URI using {@link FileSystems#newFileSystem(URI, Map)} you may have a look at {@link CryptoFileSystemUris}.
* To create a new FileSystem from a URI using {@link FileSystems#newFileSystem(URI, Map)} you may have a look at {@link CryptoFileSystemUri}.
*
* @see CryptoFileSystemUris
* @see CryptoFileSystemUri
* @see CryptoFileSystemProperties
* @see FileSystems
* @see FileSystem
Expand All @@ -98,13 +99,41 @@ public CryptoFileSystemProvider() {
}

public static CryptoFileSystem newFileSystem(Path pathToVault, CryptoFileSystemProperties properties) throws IOException {
return (CryptoFileSystem) FileSystems.newFileSystem(createUri(pathToVault.toAbsolutePath()), properties);
return (CryptoFileSystem) FileSystems.newFileSystem(create(pathToVault.toAbsolutePath()), properties);
}

/**
* Creates a new vault at the given directory path.
*
* @param pathToVault Path to a not yet existing directory
* @param masterkeyFilename Name of the masterkey file
* @param passphrase Passphrase that should be used to unlock the vault
* @throws NotDirectoryException If the given path is not an existing directory.
* @throws IOException If the vault structure could not be initialized due to I/O errors
* @since 1.3.0
*/
public static void initialize(Path pathToVault, String masterkeyFilename, CharSequence passphrase) throws NotDirectoryException, IOException {
if (!Files.isDirectory(pathToVault)) {
throw new NotDirectoryException(pathToVault.toString());
}
try (Cryptor cryptor = CRYPTOR_PROVIDER.createNew()) {
// save masterkey file:
Path masterKeyPath = pathToVault.resolve(masterkeyFilename);
byte[] keyFileContents = cryptor.writeKeysToMasterkeyFile(passphrase, Constants.VAULT_VERSION).serialize();
Files.write(masterKeyPath, keyFileContents, CREATE_NEW, WRITE);
// create "d/RO/OTDIRECTORY":
String rootDirHash = cryptor.fileNameCryptor().hashDirectoryId(Constants.ROOT_DIR_ID);
Path rootDirPath = pathToVault.resolve(Constants.DATA_DIR_NAME).resolve(rootDirHash.substring(0, 2)).resolve(rootDirHash.substring(2));
Files.createDirectories(rootDirPath);
}
assert containsVault(pathToVault, masterkeyFilename);
}

/**
* Checks if the folder represented by the given path exists and contains a valid vault structure.
*
* @param pathToVault A directory path
* @param masterkeyFilename Name of the masterkey file
* @return <code>true</code> if the directory seems to contain a vault.
* @since 1.1.0
*/
Expand All @@ -118,6 +147,7 @@ public static boolean containsVault(Path pathToVault, String masterkeyFilename)
* Changes the passphrase of a vault at the given path.
*
* @param pathToVault Vault directory
* @param masterkeyFilename Name of the masterkey file
* @param oldPassphrase Current passphrase
* @param newPassphrase Future passphrase
* @throws InvalidPassphraseException If <code>oldPassphrase</code> can not be used to unlock the vault.
Expand Down Expand Up @@ -161,25 +191,31 @@ CryptoFileSystems getCryptoFileSystems() {

@Override
public String getScheme() {
return CryptoFileSystemUris.URI_SCHEME;
return CryptoFileSystemUri.URI_SCHEME;
}

@Override
public CryptoFileSystem newFileSystem(URI uri, Map<String, ?> rawProperties) throws IOException {
ParsedUri parsedUri = CryptoFileSystemUris.parseUri(uri);
CryptoFileSystemUri parsedUri = CryptoFileSystemUri.parse(uri);
CryptoFileSystemProperties properties = CryptoFileSystemProperties.wrap(rawProperties);

// TODO remove implicit initialization in 2.0.0:
if (properties.initializeImplicitly() && !CryptoFileSystemProvider.containsVault(parsedUri.pathToVault(), properties.masterkeyFilename())) {
CryptoFileSystemProvider.initialize(parsedUri.pathToVault(), properties.masterkeyFilename(), properties.passphrase());
}

return fileSystems.create(parsedUri.pathToVault(), properties);
}

@Override
public CryptoFileSystem getFileSystem(URI uri) {
ParsedUri parsedUri = CryptoFileSystemUris.parseUri(uri);
CryptoFileSystemUri parsedUri = CryptoFileSystemUri.parse(uri);
return fileSystems.get(parsedUri.pathToVault());
}

@Override
public Path getPath(URI uri) {
ParsedUri parsedUri = CryptoFileSystemUris.parseUri(uri);
CryptoFileSystemUri parsedUri = CryptoFileSystemUri.parse(uri);
return fileSystems.get(parsedUri.pathToVault()).getPath(parsedUri.pathInsideVault());
}

Expand Down
Loading

0 comments on commit 480d27f

Please sign in to comment.