diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/Cryptor.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/Cryptor.java index eaabdc15c..514365008 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/Cryptor.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/Cryptor.java @@ -21,7 +21,7 @@ public interface Cryptor extends Destroyable { void randomizeMasterkey(); - void readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence passphrase) throws InvalidPassphraseException; + void readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence passphrase) throws InvalidPassphraseException, UnsupportedVaultFormatException; byte[] writeKeysToMasterkeyFile(CharSequence passphrase); diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/UnsupportedVaultFormatException.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/UnsupportedVaultFormatException.java new file mode 100644 index 000000000..b3d06b9f2 --- /dev/null +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/UnsupportedVaultFormatException.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2016 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 UnsupportedVaultFormatException extends CryptoException { + + private final Integer detectedVersion; + private final Integer supportedVersion; + + public UnsupportedVaultFormatException(Integer detectedVersion, Integer supportedVersion) { + super("Tried to open vault of version " + detectedVersion + ", but can only handle version " + supportedVersion); + this.detectedVersion = detectedVersion; + this.supportedVersion = supportedVersion; + } + + public Integer getDetectedVersion() { + return detectedVersion; + } + + public Integer getSupportedVersion() { + return supportedVersion; + } + + public boolean isVaultOlderThanSoftware() { + return detectedVersion == null || detectedVersion < supportedVersion; + } + + public boolean isSoftwareOlderThanVault() { + return detectedVersion > supportedVersion; + } + +} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java index 5b54a0288..dc0f0c9d4 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java @@ -5,6 +5,8 @@ public final class Constants { private Constants() { } + static final Integer CURRENT_VAULT_VERSION = 3; + public static final int PAYLOAD_SIZE = 32 * 1024; public static final int NONCE_SIZE = 16; public static final int MAC_SIZE = 32; diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java index 790d40794..2c1bebc11 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java @@ -8,6 +8,8 @@ *******************************************************************************/ package org.cryptomator.crypto.engine.impl; +import static org.cryptomator.crypto.engine.impl.Constants.CURRENT_VAULT_VERSION; + import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -25,6 +27,7 @@ import org.cryptomator.crypto.engine.Cryptor; import org.cryptomator.crypto.engine.FileContentCryptor; import org.cryptomator.crypto.engine.FilenameCryptor; import org.cryptomator.crypto.engine.InvalidPassphraseException; +import org.cryptomator.crypto.engine.UnsupportedVaultFormatException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -97,10 +100,8 @@ class CryptorImpl implements Cryptor { } // check version - if (keyFile.getVersion() != KeyFile.CURRENT_VERSION) { - // TODO - // throw new UnsupportedVaultException(keyfile.getVersion(), KeyFile.CURRENT_VERSION); - throw new IllegalArgumentException("Unsupported key (expected version: " + KeyFile.CURRENT_VERSION + ", actual version: " + keyFile.getVersion() + ")"); + if (keyFile.getVersion() != CURRENT_VAULT_VERSION) { + throw new UnsupportedVaultFormatException(keyFile.getVersion(), CURRENT_VAULT_VERSION); } final byte[] kekBytes = Scrypt.scrypt(passphrase, keyFile.getScryptSalt(), keyFile.getScryptCostParam(), keyFile.getScryptBlockSize(), KEYLENGTH_IN_BYTES); @@ -134,7 +135,7 @@ class CryptorImpl implements Cryptor { } final KeyFile keyfile = new KeyFile(); - keyfile.setVersion(KeyFile.CURRENT_VERSION); + keyfile.setVersion(CURRENT_VAULT_VERSION); keyfile.setScryptSalt(scryptSalt); keyfile.setScryptCostParam(SCRYPT_COST_PARAM); keyfile.setScryptBlockSize(SCRYPT_BLOCK_SIZE); diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/KeyFile.java b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/KeyFile.java index a40c3e61e..829f7ed39 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/KeyFile.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/KeyFile.java @@ -10,13 +10,14 @@ package org.cryptomator.crypto.engine.impl; import java.io.Serializable; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +@JsonIgnoreProperties(ignoreUnknown = true) @JsonPropertyOrder(value = {"version", "scryptSalt", "scryptCostParam", "scryptBlockSize", "primaryMasterKey", "hmacMasterKey"}) class KeyFile implements Serializable { - static final Integer CURRENT_VERSION = 3; private static final long serialVersionUID = 8578363158959619885L; @JsonProperty("version") diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Constants.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Constants.java new file mode 100644 index 000000000..7e6d31af0 --- /dev/null +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Constants.java @@ -0,0 +1,16 @@ +package org.cryptomator.filesystem.crypto; + +public final class Constants { + + 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"; + + static final String DIR_SUFFIX = "_"; + +} diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystem.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystem.java index 25007a197..892c96b5c 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystem.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFileSystem.java @@ -8,6 +8,9 @@ *******************************************************************************/ package org.cryptomator.filesystem.crypto; +import static org.cryptomator.filesystem.crypto.Constants.DATA_ROOT_DIR; +import static org.cryptomator.filesystem.crypto.Constants.ROOT_DIRECOTRY_ID; + import java.io.UncheckedIOException; import java.time.Instant; import java.util.Optional; @@ -20,9 +23,6 @@ import org.cryptomator.filesystem.Folder; class CryptoFileSystem extends CryptoFolder implements FileSystem { - private static final String DATA_ROOT_DIR = "d"; - private static final String ROOT_DIRECOTRY_ID = ""; - private final Folder physicalRoot; private final CryptoFileSystemDelegate delegate; diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java index 66551b7f3..8463ee14d 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoFolder.java @@ -9,6 +9,7 @@ package org.cryptomator.filesystem.crypto; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.cryptomator.filesystem.crypto.Constants.DIR_SUFFIX; import java.io.FileNotFoundException; import java.io.UncheckedIOException; @@ -31,8 +32,6 @@ import org.cryptomator.io.FileContents; class CryptoFolder extends CryptoNode implements Folder { - static final String DIR_SUFFIX = "_"; - private final WeakValuedCache folders = WeakValuedCache.usingLoader(this::newFolder); private final WeakValuedCache files = WeakValuedCache.usingLoader(this::newFile); private final AtomicReference directoryId = new AtomicReference<>(); @@ -114,7 +113,7 @@ class CryptoFolder extends CryptoNode implements Folder { private String decryptChildFolderName(String encryptedFolderName) { final byte[] dirId = getDirectoryId().get().getBytes(UTF_8); - final String ciphertext = StringUtils.removeEnd(encryptedFolderName, CryptoFolder.DIR_SUFFIX); + final String ciphertext = StringUtils.removeEnd(encryptedFolderName, DIR_SUFFIX); return cryptor.getFilenameCryptor().decryptFilename(ciphertext, dirId); } diff --git a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Masterkeys.java b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Masterkeys.java index 033484080..c58c31384 100644 --- a/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Masterkeys.java +++ b/main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/Masterkeys.java @@ -8,6 +8,9 @@ *******************************************************************************/ package org.cryptomator.filesystem.crypto; +import static org.cryptomator.filesystem.crypto.Constants.MASTERKEY_BACKUP_FILENAME; +import static org.cryptomator.filesystem.crypto.Constants.MASTERKEY_FILENAME; + import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; @@ -28,9 +31,6 @@ import org.cryptomator.filesystem.WritableFile; @Singleton class Masterkeys { - private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; - private static final String MASTERKEY_BACKUP_FILENAME = "masterkey.cryptomator.bkup"; - private final Provider cryptorProvider; @Inject diff --git a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/CryptorImplTest.java b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/CryptorImplTest.java index 93ede3093..460976134 100644 --- a/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/CryptorImplTest.java +++ b/main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/CryptorImplTest.java @@ -12,6 +12,7 @@ import java.io.IOException; import org.cryptomator.crypto.engine.Cryptor; import org.cryptomator.crypto.engine.InvalidPassphraseException; +import org.cryptomator.crypto.engine.UnsupportedVaultFormatException; import org.junit.Assert; import org.junit.Test; @@ -35,6 +36,15 @@ public class CryptorImplTest { cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "qwe"); } + @Test(expected = UnsupportedVaultFormatException.class) + 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==\"}"; + final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); + cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "qwe"); + } + @Test public void testMasterkeyEncryption() throws IOException { final String expectedMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":16384,\"scryptBlockSize\":8," // diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java index 3f4f8e1bf..f9e4c2109 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java @@ -20,6 +20,7 @@ import javax.inject.Inject; import org.apache.commons.lang3.CharUtils; import org.apache.commons.lang3.SystemUtils; import org.cryptomator.crypto.engine.InvalidPassphraseException; +import org.cryptomator.crypto.engine.UnsupportedVaultFormatException; import org.cryptomator.frontend.CommandFailedException; import org.cryptomator.frontend.FrontendCreationFailedException; import org.cryptomator.frontend.webdav.mount.WindowsDriveLetters; @@ -261,20 +262,21 @@ public class UnlockController extends AbstractFXMLViewController { progressIndicator.setVisible(false); messageText.setText(resourceBundle.getString("unlock.errorMessage.wrongPassword")); Platform.runLater(passwordField::requestFocus); + } catch (UnsupportedVaultFormatException e) { + setControlsDisabled(false); + progressIndicator.setVisible(false); + downloadsPageLink.setVisible(true); + LOG.warn("Unable to unlock vault: " + e.getMessage()); + if (e.isVaultOlderThanSoftware()) { + messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " "); + } else if (e.isSoftwareOlderThanVault()) { + messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " "); + } } catch (FrontendCreationFailedException ex) { setControlsDisabled(false); progressIndicator.setVisible(false); messageText.setText(resourceBundle.getString("unlock.errorMessage.decryptionFailed")); LOG.error("Decryption failed for technical reasons.", ex); - // } catch (UnsupportedVaultException e) { - // setControlsDisabled(false); - // progressIndicator.setVisible(false); - // downloadsPageLink.setVisible(true); - // if (e.isVaultOlderThanSoftware()) { - // messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " "); - // } else if (e.isSoftwareOlderThanVault()) { - // messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " "); - // } } finally { passwordField.swipe(); }