Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ Changes to prior versions can be found on the [GitHub release page](https://gith

## [Unreleased](https://github.com/cryptomator/cryptofs/compare/2.10.0...HEAD)

### Added
* Property `vaultId` in `CryptoFileSystemProperties` to set the vault id during vault initialization ([#358](https://github.com/cryptomator/cryptofs/pull/358))
* Getter `UnverifiedVaultConfig::allegedVaultId` to get the vault id of an unverified vault config ([#358](https://github.com/cryptomator/cryptofs/pull/358))

### Changed
* Updated dependencies ([#357](https://github.com/cryptomator/cryptofs/pull/357)):
* `com.auth0:java-jwt` from 4.5.1 to 4.6.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
*******************************************************************************/
package org.cryptomator.cryptofs;

import com.google.common.base.Strings;
import org.cryptomator.cryptofs.event.FilesystemEvent;
import org.cryptomator.cryptolib.api.CryptorProvider;
import org.cryptomator.cryptolib.api.MasterkeyLoader;
Expand All @@ -19,7 +18,9 @@
import java.util.AbstractMap;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
Expand Down Expand Up @@ -116,6 +117,13 @@ public enum FileSystemFlags {

static final CryptorProvider.Scheme DEFAULT_CIPHER_COMBO = CryptorProvider.Scheme.SIV_GCM;

/**
* Key identifying the id of a vault. Only meaningful during vault initialization. If absent, a random id is generated.
*
* @since 2.11.0
*/
public static final String PROPERTY_VAULT_ID = "vaultId";

/**
* Key identifying the filesystem owner supply method.
*
Expand All @@ -128,7 +136,7 @@ public enum FileSystemFlags {
private final Set<Entry<String, Object>> entries;

private CryptoFileSystemProperties(Builder builder) {
this.entries = Set.of( //
var entries = new HashSet<Entry<String, Object>>(Set.of( //
Map.entry(PROPERTY_KEYLOADER, builder.keyLoader), //
Map.entry(PROPERTY_FILESYSTEM_FLAGS, builder.flags), //
Map.entry(PROPERTY_VAULTCONFIG_FILENAME, builder.vaultConfigFilename), //
Expand All @@ -138,7 +146,11 @@ private CryptoFileSystemProperties(Builder builder) {
Map.entry(PROPERTY_SHORTENING_THRESHOLD, builder.shorteningThreshold), //
Map.entry(PROPERTY_CIPHER_COMBO, builder.cipherCombo), //
Map.entry(PROPERTY_OWNER_GETTER, builder.ownerGetter) //
);
));
if (builder.vaultId != null) {
entries.add(Map.entry(PROPERTY_VAULT_ID, builder.vaultId));
}
this.entries = Set.copyOf(entries);
}

MasterkeyLoader keyLoader() {
Expand All @@ -149,6 +161,14 @@ public CryptorProvider.Scheme cipherCombo() {
return (CryptorProvider.Scheme) get(PROPERTY_CIPHER_COMBO);
}

/**
* @return the vault id to use during vault initialization, or empty if a random id should be generated
* @since 2.11.0
*/
public Optional<String> vaultId() {
return Optional.ofNullable((String) get(PROPERTY_VAULT_ID));
}

@SuppressWarnings("unchecked")
public Set<FileSystemFlags> flags() {
return (Set<FileSystemFlags>) get(PROPERTY_FILESYSTEM_FLAGS);
Expand Down Expand Up @@ -233,6 +253,7 @@ public static CryptoFileSystemProperties wrap(Map<String, ?> properties) {
public static class Builder {

public CryptorProvider.Scheme cipherCombo = DEFAULT_CIPHER_COMBO;
private String vaultId = null;
private MasterkeyLoader keyLoader = null;
private final Set<FileSystemFlags> flags = EnumSet.copyOf(DEFAULT_FILESYSTEM_FLAGS);
private String vaultConfigFilename = DEFAULT_VAULTCONFIG_FILENAME;
Expand All @@ -253,6 +274,7 @@ private Builder(Map<String, ?> properties) {
checkedSet(Integer.class, PROPERTY_MAX_CLEARTEXT_NAME_LENGTH, properties, this::withMaxCleartextNameLength);
checkedSet(Integer.class, PROPERTY_SHORTENING_THRESHOLD, properties, this::withShorteningThreshold);
checkedSet(CryptorProvider.Scheme.class, PROPERTY_CIPHER_COMBO, properties, this::withCipherCombo);
checkedSet(String.class, PROPERTY_VAULT_ID, properties, this::withVaultId);
checkedSet(Consumer.class, PROPERTY_EVENT_CONSUMER, properties, this::withFilesystemEventConsumer);
checkedSet(Supplier.class, PROPERTY_OWNER_GETTER, properties, this::withOwnerGetter);
}
Expand Down Expand Up @@ -307,6 +329,21 @@ public Builder withCipherCombo(CryptorProvider.Scheme cipherCombo) {
return this;
}

/**
* Sets the vault id used during vault initialization. If not set, a random id is generated.
*
* @param vaultId The id of the vault
* @return this
* @since 2.11.0
*/
public Builder withVaultId(String vaultId) {
if (vaultId == null || vaultId.isBlank()) {
throw new IllegalArgumentException("Parameter vaultId must not be null or blank");
}
this.vaultId = vaultId;
return this;
}

/**
* Sets the keyloader for a CryptoFileSystem.
*
Expand Down Expand Up @@ -417,7 +454,7 @@ private void validate() {
if (keyLoader == null) {
throw new IllegalStateException("keyLoader is required");
}
if (Strings.nullToEmpty(masterkeyFilename).trim().isEmpty()) {
if (masterkeyFilename == null || masterkeyFilename.isBlank()) {
throw new IllegalStateException("masterkeyFilename is required");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,11 @@ public static void initialize(Path pathToVault, CryptoFileSystemProperties prope
throw new NotDirectoryException(pathToVault.toString());
}
byte[] rawKey = new byte[0];
var config = VaultConfig.createNew().cipherCombo(properties.cipherCombo()).shorteningThreshold(properties.shorteningThreshold()).build();
var configBuilder = VaultConfig.createNew() //
.cipherCombo(properties.cipherCombo()) //
.shorteningThreshold(properties.shorteningThreshold());
properties.vaultId().ifPresent(configBuilder::vaultId);
var config = configBuilder.build();
try (Masterkey key = properties.keyLoader().loadKey(keyId); //
Cryptor cryptor = CryptorProvider.forScheme(config.getCipherCombo()).provide(key, strongSecureRandom())) {
rawKey = key.getEncoded();
Expand Down
14 changes: 13 additions & 1 deletion src/main/java/org/cryptomator/cryptofs/VaultConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ public int allegedShorteningThreshold() {
return unverifiedConfig.getClaim(JSON_KEY_SHORTENING_THRESHOLD).asInt();
}

/**
* @return The unverified vault id (signature not verified)
*/
public String allegedVaultId() {
return unverifiedConfig.getId();
}

private Algorithm initAlgorithm(byte[] rawKey) throws VaultConfigLoadException {
var algo = unverifiedConfig.getAlgorithm();
return switch (algo) {
Expand Down Expand Up @@ -211,11 +218,16 @@ public VaultConfig verify(byte[] rawKey, int expectedVaultVersion) throws VaultK

public static class VaultConfigBuilder {

private final String id = UUID.randomUUID().toString();
private final int vaultVersion = Constants.VAULT_VERSION;
private String id = UUID.randomUUID().toString();
private CryptorProvider.Scheme cipherCombo;
private int shorteningThreshold;

public VaultConfigBuilder vaultId(String vaultId) {
this.id = vaultId;
return this;
}

public VaultConfigBuilder cipherCombo(CryptorProvider.Scheme cipherCombo) {
this.cipherCombo = cipherCombo;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;

import static org.cryptomator.cryptofs.CryptoFileSystemProperties.*;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;

public class CryptoFileSystemPropertiesTest {
Expand Down Expand Up @@ -86,6 +88,46 @@ public void testFromMap() {
);
}

@Test
public void testVaultIdAbsentByDefault() {
CryptoFileSystemProperties inTest = cryptoFileSystemProperties().withKeyLoader(keyLoader).build();

Assertions.assertTrue(inTest.vaultId().isEmpty());
Assertions.assertFalse(inTest.containsKey(PROPERTY_VAULT_ID));
}

@Test
public void testSetVaultId() {
CryptoFileSystemProperties inTest = cryptoFileSystemProperties().withKeyLoader(keyLoader).withVaultId("my-vault-id").build();

Assertions.assertEquals(Optional.of("my-vault-id"), inTest.vaultId());
MatcherAssert.assertThat(inTest.entrySet(), hasItem(anEntry(PROPERTY_VAULT_ID, "my-vault-id")));
}

@Test
public void testVaultIdFromMap() {
Map<String, Object> map = new HashMap<>();
map.put(PROPERTY_KEYLOADER, keyLoader);
map.put(PROPERTY_VAULT_ID, "my-vault-id");
CryptoFileSystemProperties inTest = cryptoFileSystemPropertiesFrom(map).build();

Assertions.assertEquals(Optional.of("my-vault-id"), inTest.vaultId());
}

@Test
public void testBlankVaultIdThrowsIAE() {
Assertions.assertThrows(IllegalArgumentException.class, () -> cryptoFileSystemProperties().withVaultId(" "));
}

@Test
public void testWrapMapWithInvalidVaultId() {
Map<String, Object> map = new HashMap<>();
map.put(PROPERTY_KEYLOADER, keyLoader);
map.put(PROPERTY_VAULT_ID, 42);

Assertions.assertThrows(IllegalArgumentException.class, () -> CryptoFileSystemProperties.wrap(map));
}

@Test
public void testWrapMapWithTrueReadonly() {
Map<String, Object> map = new HashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,20 @@ public void testInitialize() throws IOException, MasterkeyLoadingFailedException
Assertions.assertEquals(Constants.DIR_ID_BACKUP_FILE_NAME, dirIdBackup.get().getFileName().toString());
}

@Test
public void testInitializeWithVaultId() throws IOException, MasterkeyLoadingFailedException {
FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
Path pathToVault = fs.getPath("/vaultDir");
Path vaultConfigFile = pathToVault.resolve("vault.cryptomator");
var properties = cryptoFileSystemProperties().withKeyLoader(keyLoader).withVaultId("my-vault-id").build();

Files.createDirectory(pathToVault);
CryptoFileSystemProvider.initialize(pathToVault, properties, URI.create("test:key"));

var config = VaultConfig.decode(Files.readString(vaultConfigFile));
Assertions.assertEquals("my-vault-id", config.allegedVaultId());
}

@Test
public void testNewFileSystem() throws IOException, MasterkeyLoadingFailedException {
Path pathToVault = Path.of("/vaultDir");
Expand Down
20 changes: 19 additions & 1 deletion src/test/java/org/cryptomator/cryptofs/VaultConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ public void testToToken() {
@DisplayName("Using valid tokens...")
public class WithValidToken {

private static final String TOKEN_NONE = "eyJraWQiOiJURVNUX0tFWSIsInR5cCI6IkpXVCIsImFsZyI6Im5vbmUifQ.eyJmb3JtYXQiOjgsInNob3J0ZW5pbmdUaHJlc2hvbGQiOjIyMCwianRpIjoiZjRiMjlmM2EtNDdkNi00NjlmLTk2NGMtZjRjMmRhZWU4ZWI2IiwiY2lwaGVyQ29tYm8iOiJTSVZfQ1RSTUFDIn0.";
private static final String VAULT_ID = "f4b29f3a-47d6-469f-964c-f4c2daee8eb6";
private static final String TOKEN_NONE ="eyJraWQiOiJURVNUX0tFWSIsInR5cCI6IkpXVCIsImFsZyI6Im5vbmUifQ.eyJmb3JtYXQiOjgsInNob3J0ZW5pbmdUaHJlc2hvbGQiOjIyMCwianRpIjoiZjRiMjlmM2EtNDdkNi00NjlmLTk2NGMtZjRjMmRhZWU4ZWI2IiwiY2lwaGVyQ29tYm8iOiJTSVZfQ1RSTUFDIn0.";
private static final String TOKEN_HS256 = "eyJraWQiOiJURVNUX0tFWSIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJmb3JtYXQiOjgsInNob3J0ZW5pbmdUaHJlc2hvbGQiOjIyMCwianRpIjoiZjRiMjlmM2EtNDdkNi00NjlmLTk2NGMtZjRjMmRhZWU4ZWI2IiwiY2lwaGVyQ29tYm8iOiJTSVZfQ1RSTUFDIn0.V7pqSXX1tBRgmntL1sXovnhNR4Z1_7z3Jzrq7NMqPO8";
private static final String TOKEN_HS384 = "eyJraWQiOiJURVNUX0tFWSIsInR5cCI6IkpXVCIsImFsZyI6IkhTMzg0In0.eyJmb3JtYXQiOjgsInNob3J0ZW5pbmdUaHJlc2hvbGQiOjIyMCwianRpIjoiZjRiMjlmM2EtNDdkNi00NjlmLTk2NGMtZjRjMmRhZWU4ZWI2IiwiY2lwaGVyQ29tYm8iOiJTSVZfQ1RSTUFDIn0.rx03sCVAyrCmT6halPaFU46lu-DOd03iwDgvdw362hfgJj782q6xPXjAxdKeVKxG";
private static final String TOKEN_HS512 = "eyJraWQiOiJURVNUX0tFWSIsInR5cCI6IkpXVCIsImFsZyI6IkhTNTEyIn0.eyJmb3JtYXQiOjgsInNob3J0ZW5pbmdUaHJlc2hvbGQiOjIyMCwianRpIjoiZjRiMjlmM2EtNDdkNi00NjlmLTk2NGMtZjRjMmRhZWU4ZWI2IiwiY2lwaGVyQ29tYm8iOiJTSVZfQ1RSTUFDIn0.fzkVI34Ou3z7RaFarS9VPCaA0NX9z7My14gAISTXJGKGNSID7xEcoaY56SBdWbU7Ta17KhxcHhbXffxk3Mzing";
Expand All @@ -95,6 +96,15 @@ public void testSuccessfulLoad(String algo, String token) throws VaultConfigLoad
Assertions.assertEquals(8, loaded.getVaultVersion());
Assertions.assertEquals(CryptorProvider.Scheme.SIV_CTRMAC, loaded.getCipherCombo());
Assertions.assertEquals(220, loaded.getShorteningThreshold());
Assertions.assertEquals(VAULT_ID, loaded.getId());
}

@Test
@DisplayName("allegedVaultId() reads jti without verification")
public void testAllegedVaultId() throws VaultConfigLoadException {
var unverified = VaultConfig.decode(TOKEN_HS256);

Assertions.assertEquals(VAULT_ID, unverified.allegedVaultId());
}

@DisplayName("load using key with...")
Expand All @@ -121,6 +131,14 @@ public void testCreateNew() {
Assertions.assertEquals(220, config.getShorteningThreshold());
}

@Test
@DisplayName("test VaultConfig.createNew().vaultId(...)")
public void testCreateNewWithVaultId() {
var config = VaultConfig.createNew().cipherCombo(CryptorProvider.Scheme.SIV_CTRMAC).shorteningThreshold(220).vaultId("my-vault-id").build();

Assertions.assertEquals("my-vault-id", config.getId());
}

@Test
@DisplayName("test VaultConfig.load(...)")
public void testLoadExisting() throws VaultConfigLoadException, MasterkeyLoadingFailedException {
Expand Down
Loading