Skip to content

Commit

Permalink
Merge branch 'feature/vault-version-4' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
overheadhunter committed Jul 3, 2016
2 parents 034b5c2 + a9e0dfd commit 6283d2d
Show file tree
Hide file tree
Showing 22 changed files with 401 additions and 135 deletions.
2 changes: 1 addition & 1 deletion main/filesystem-charsets/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<version>1.2.0-SNAPSHOT</version>
</parent>
<artifactId>filesystem-charsets</artifactId>
<name>Cryptomator filesystem: Filename charset compatibility layer</name>
<name>Cryptomator filesystem: Charset compatibility layer</name>

<dependencies>
<dependency>
Expand Down
2 changes: 1 addition & 1 deletion main/filesystem-crypto/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<properties>
<bouncycastle.version>1.51</bouncycastle.version>
<sivmode.version>1.0.2</sivmode.version>
<sivmode.version>1.0.4</sivmode.version>
</properties>

<dependencies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,28 @@
public class UnsupportedVaultFormatException extends CryptoException {

private final Integer detectedVersion;
private final Integer supportedVersion;
private final Integer latestSupportedVersion;

public UnsupportedVaultFormatException(Integer detectedVersion, Integer supportedVersion) {
super("Tried to open vault of version " + detectedVersion + ", but can only handle version " + supportedVersion);
public UnsupportedVaultFormatException(Integer detectedVersion, Integer latestSupportedVersion) {
super("Tried to open vault of version " + detectedVersion + ", latest supported version is " + latestSupportedVersion);
this.detectedVersion = detectedVersion;
this.supportedVersion = supportedVersion;
this.latestSupportedVersion = latestSupportedVersion;
}

public Integer getDetectedVersion() {
return detectedVersion;
}

public Integer getSupportedVersion() {
return supportedVersion;
public Integer getLatestSupportedVersion() {
return latestSupportedVersion;
}

public boolean isVaultOlderThanSoftware() {
return detectedVersion == null || detectedVersion < supportedVersion;
return detectedVersion == null || detectedVersion < latestSupportedVersion;
}

public boolean isSoftwareOlderThanVault() {
return detectedVersion > supportedVersion;
return detectedVersion > latestSupportedVersion;
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package org.cryptomator.crypto.engine.impl;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;

public final class Constants {

private Constants() {
}

static final Integer CURRENT_VAULT_VERSION = 3;
static final Collection<Integer> SUPPORTED_VAULT_VERSIONS = Collections.unmodifiableCollection(Arrays.asList(3, 4));
static final Integer CURRENT_VAULT_VERSION = 4;

public static final int PAYLOAD_SIZE = 32 * 1024;
public static final int NONCE_SIZE = 16;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
package org.cryptomator.crypto.engine.impl;

import static org.cryptomator.crypto.engine.impl.Constants.CURRENT_VAULT_VERSION;
import static org.cryptomator.crypto.engine.impl.Constants.SUPPORTED_VAULT_VERSIONS;

import java.io.IOException;
import java.nio.ByteBuffer;
Expand Down Expand Up @@ -109,7 +110,7 @@ public void readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence
assert keyFile != null;

// check version
if (!CURRENT_VAULT_VERSION.equals(keyFile.getVersion())) {
if (!SUPPORTED_VAULT_VERSIONS.contains(keyFile.getVersion())) {
throw new UnsupportedVaultFormatException(keyFile.getVersion(), CURRENT_VAULT_VERSION);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
class FilenameCryptorImpl implements FilenameCryptor {

private static final BaseNCodec BASE32 = new Base32();
private static final Pattern BASE32_PATTERN = Pattern.compile("([A-Z0-9]{8})*[A-Z0-9=]{8}");
// https://tools.ietf.org/html/rfc4648#section-6
private static final Pattern BASE32_PATTERN = Pattern.compile("([A-Z2-7]{8})*[A-Z2-7=]{8}");
private static final ThreadLocal<MessageDigest> SHA1 = new ThreadLocalSha1();
private static final ThreadLocal<SivMode> AES_SIV = new ThreadLocal<SivMode>() {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.cryptomator.filesystem.crypto;

import static org.cryptomator.filesystem.crypto.Constants.DIR_SUFFIX;
import static org.cryptomator.filesystem.crypto.Constants.DIR_PREFIX;

import java.util.Optional;
import java.util.UUID;
Expand Down Expand Up @@ -32,7 +32,7 @@ public ConflictResolver(Pattern encryptedNamePattern, Function<String, Optional<
}

public File resolveIfNecessary(File file) {
Matcher m = encryptedNamePattern.matcher(StringUtils.removeEnd(file.name(), DIR_SUFFIX));
Matcher m = encryptedNamePattern.matcher(StringUtils.removeStart(file.name(), DIR_PREFIX));
if (m.matches()) {
// full match, use file as is
return file;
Expand All @@ -47,11 +47,11 @@ public File resolveIfNecessary(File file) {

private File resolveConflict(File conflictingFile, MatchResult matchResult) {
String ciphertext = matchResult.group();
boolean isDirectory = conflictingFile.name().substring(matchResult.end()).startsWith(DIR_SUFFIX);
boolean isDirectory = conflictingFile.name().startsWith(DIR_PREFIX);
Optional<String> cleartext = nameDecryptor.apply(ciphertext);
if (cleartext.isPresent()) {
Folder folder = conflictingFile.parent().get();
File canonicalFile = folder.file(isDirectory ? ciphertext + DIR_SUFFIX : ciphertext);
File canonicalFile = folder.file(isDirectory ? DIR_PREFIX + ciphertext : ciphertext);
if (canonicalFile.exists()) {
// there must not be two directories pointing to the same directory id. In this case no human interaction is needed to resolve this conflict:
if (isDirectory && FileContents.UTF_8.readContents(canonicalFile).equals(FileContents.UTF_8.readContents(conflictingFile))) {
Expand All @@ -66,7 +66,7 @@ private File resolveConflict(File conflictingFile, MatchResult matchResult) {
conflictId = createConflictId();
String alternativeCleartext = cleartext.get() + " (Conflict " + conflictId + ")";
String alternativeCiphertext = nameEncryptor.apply(alternativeCleartext).get();
alternativeFile = folder.file(isDirectory ? alternativeCiphertext + DIR_SUFFIX : alternativeCiphertext);
alternativeFile = folder.file(isDirectory ? DIR_PREFIX + alternativeCiphertext : alternativeCiphertext);
} while (alternativeFile.exists());
LOG.info("Detected conflict {}:\n{}\n{}", conflictId, canonicalFile, conflictingFile);
conflictingFile.moveTo(alternativeFile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ private Constants() {
static final String DATA_ROOT_DIR = "d";
static final String ROOT_DIRECOTRY_ID = "";

static final String MASTERKEY_FILENAME = "masterkey.cryptomator";
static final String MASTERKEY_BACKUP_FILENAME = "masterkey.cryptomator.bkup";
public static final String MASTERKEY_FILENAME = "masterkey.cryptomator";
public static final String MASTERKEY_BACKUP_FILENAME = "masterkey.cryptomator.bkup";

static final String DIR_SUFFIX = "_";
static final String DIR_PREFIX = "0";

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.cryptomator.filesystem.crypto.Constants.DIR_SUFFIX;
import static org.cryptomator.filesystem.crypto.Constants.DIR_PREFIX;

import java.io.FileNotFoundException;
import java.io.UncheckedIOException;
Expand Down Expand Up @@ -54,9 +54,9 @@ public CryptoFolder(CryptoFolder parent, String name, Cryptor cryptor) {
@Override
protected Optional<String> encryptedName() {
if (parent().isPresent()) {
return parent().get().encryptChildName(name()).map(s -> s + DIR_SUFFIX);
return parent().get().encryptChildName(name()).map(s -> DIR_PREFIX + s);
} else {
return Optional.of(cryptor.getFilenameCryptor().encryptFilename(name()) + DIR_SUFFIX);
return Optional.of(DIR_PREFIX + cryptor.getFilenameCryptor().encryptFilename(name()));
}
}

Expand Down Expand Up @@ -121,20 +121,20 @@ Optional<String> encryptChildName(String cleartextFileName) {

@Override
public Stream<CryptoFile> files() {
return nonConflictingFiles().map(File::name).filter(endsWithDirSuffix().negate()).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::file);
return nonConflictingFiles().map(File::name).filter(startsWithDirPrefix().negate()).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::file);
}

@Override
public Stream<CryptoFolder> folders() {
return nonConflictingFiles().map(File::name).filter(endsWithDirSuffix()).map(this::removeDirSuffix).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::folder);
return nonConflictingFiles().map(File::name).filter(startsWithDirPrefix()).map(this::removeDirPrefix).map(this::decryptChildName).filter(Optional::isPresent).map(Optional::get).map(this::folder);
}

private Predicate<String> endsWithDirSuffix() {
return (String encryptedFolderName) -> StringUtils.endsWith(encryptedFolderName, DIR_SUFFIX);
private Predicate<String> startsWithDirPrefix() {
return (String encryptedFolderName) -> StringUtils.startsWith(encryptedFolderName, DIR_PREFIX);
}

private String removeDirSuffix(String encryptedFolderName) {
return StringUtils.removeEnd(encryptedFolderName, DIR_SUFFIX);
private String removeDirPrefix(String encryptedFolderName) {
return StringUtils.removeStart(encryptedFolderName, DIR_PREFIX);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,20 @@ public class CryptorImplTest {

@Test
public void testMasterkeyDecryptionWithCorrectPassphrase() throws IOException {
final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
final String testMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
+ "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
+ "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
+ "\"versionMac\":\"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLA=\"}";
+ "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}";
final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl();
cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd");
}

@Test(expected = InvalidPassphraseException.class)
public void testMasterkeyDecryptionWithWrongPassphrase() throws IOException {
final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
final String testMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
+ "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
+ "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
+ "\"versionMac\":\"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLA=\"}";
+ "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}";
final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl();
cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "qwe");
}
Expand All @@ -44,7 +44,7 @@ public void testMasterkeyDecryptionWithWrongVaultFormat() throws IOException {
final String testMasterKey = "{\"version\":-1,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
+ "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
+ "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
+ "\"versionMac\":\"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLA=\"}";
+ "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}";
final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl();
cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd");
}
Expand All @@ -62,23 +62,24 @@ public void testMasterkeyDecryptionWithMissingVersionMac() throws IOException {
@Ignore
@Test(expected = UnsupportedVaultFormatException.class)
public void testMasterkeyDecryptionWithWrongVersionMac() throws IOException {
final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
final String testMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
+ "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
+ "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
+ "\"versionMac\":\"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLa=\"}";
+ "\"versionMac\":\"z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfoK=\"}";
final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl();
cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd");
}

@Test
public void testMasterkeyEncryption() throws IOException {
final String expectedMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":16384,\"scryptBlockSize\":8," //
final String expectedMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":16384,\"scryptBlockSize\":8," //
+ "\"primaryMasterKey\":\"BJPIq5pvhN24iDtPJLMFPLaVJWdGog9k4n0P03j4ru+ivbWY9OaRGQ==\"," //
+ "\"hmacMasterKey\":\"BJPIq5pvhN24iDtPJLMFPLaVJWdGog9k4n0P03j4ru+ivbWY9OaRGQ==\"," //
+ "\"versionMac\":\"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLA=\"}";
+ "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}";
final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl();
cryptor.randomizeMasterkey();
final byte[] masterkeyFile = cryptor.writeKeysToMasterkeyFile("asd");
System.out.println(new String(masterkeyFile));
Assert.assertArrayEquals(expectedMasterKey.getBytes(), masterkeyFile);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void setup() {
unrelatedFile = Mockito.mock(File.class);

String canonicalFileName = encode.apply("test name").get();
String canonicalFolderName = canonicalFileName + Constants.DIR_SUFFIX;
String canonicalFolderName = Constants.DIR_PREFIX + canonicalFileName;
String conflictingFileName = canonicalFileName + " (version 2)";
String conflictingFolderName = canonicalFolderName + " (version 2)";
String unrelatedName = "notBa$e32!";
Expand All @@ -70,6 +70,7 @@ public void setup() {
Mockito.doReturn(Optional.of(folder)).when(unrelatedFile).parent();

Mockito.when(folder.file(Mockito.startsWith(canonicalFileName.substring(0, 8)))).thenReturn(resolved);
Mockito.when(folder.file(Mockito.startsWith(canonicalFolderName.substring(0, 8)))).thenReturn(resolved);
Mockito.when(folder.file(canonicalFileName)).thenReturn(canonicalFile);
Mockito.when(folder.file(canonicalFolderName)).thenReturn(canonicalFolder);
Mockito.when(folder.file(conflictingFileName)).thenReturn(conflictingFile);
Expand Down
7 changes: 0 additions & 7 deletions main/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,6 @@
<dagger.version>2.4</dagger.version>
</properties>

<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>

<dependencyManagement>
<dependencies>
<!-- modules -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.controls.DirectoryListCell;
import org.cryptomator.ui.model.UpgradeStrategies;
import org.cryptomator.ui.model.Vault;
import org.cryptomator.ui.model.VaultFactory;
import org.cryptomator.ui.settings.Localization;
Expand Down Expand Up @@ -78,6 +79,7 @@ public class MainController extends LocalizedFXMLViewController {
private final Provider<UnlockedController> unlockedControllerProvider;
private final Lazy<ChangePasswordController> changePasswordController;
private final Lazy<SettingsController> settingsController;
private final Lazy<UpgradeStrategies> upgradeStrategies;
private final ObjectProperty<AbstractFXMLViewController> activeController = new SimpleObjectProperty<>();
private final ObservableList<Vault> vaults;
private final ObjectProperty<Vault> selectedVault = new SimpleObjectProperty<>();
Expand All @@ -90,7 +92,7 @@ public class MainController extends LocalizedFXMLViewController {
@Inject
public MainController(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings, VaultFactory vaultFactoy, Lazy<WelcomeController> welcomeController,
Lazy<InitializeController> initializeController, Lazy<NotFoundController> notFoundController, Lazy<UpgradeController> upgradeController, Lazy<UnlockController> unlockController,
Provider<UnlockedController> unlockedControllerProvider, Lazy<ChangePasswordController> changePasswordController, Lazy<SettingsController> settingsController) {
Provider<UnlockedController> unlockedControllerProvider, Lazy<ChangePasswordController> changePasswordController, Lazy<SettingsController> settingsController, Lazy<UpgradeStrategies> upgradeStrategies) {
super(localization);
this.mainWindow = mainWindow;
this.vaultFactoy = vaultFactoy;
Expand All @@ -102,6 +104,7 @@ public MainController(@Named("mainWindow") Stage mainWindow, Localization locali
this.unlockedControllerProvider = unlockedControllerProvider;
this.changePasswordController = changePasswordController;
this.settingsController = settingsController;
this.upgradeStrategies = upgradeStrategies;
this.vaults = FXCollections.observableList(settings.getDirectories());
this.vaults.addListener((Change<? extends Vault> c) -> {
settings.save();
Expand Down Expand Up @@ -288,7 +291,7 @@ private void selectedVaultDidChange(Vault newValue) {
this.showUnlockedView(newValue);
} else if (!newValue.doesVaultDirectoryExist()) {
this.showNotFoundView();
} else if (newValue.isValidVaultDirectory() && newValue.needsUpgrade()) {
} else if (newValue.isValidVaultDirectory() && upgradeStrategies.get().getUpgradeStrategy(newValue).isPresent()) {
this.showUpgradeView();
} else if (newValue.isValidVaultDirectory()) {
this.showUnlockView();
Expand Down

0 comments on commit 6283d2d

Please sign in to comment.