Browse Source

check vault version before unlocking

Sebastian Stenzel 9 năm trước cách đây
mục cha
commit
bc9b7c3a19

+ 1 - 1
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);
 

+ 38 - 0
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;
+	}
+
+}

+ 2 - 0
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;

+ 6 - 5
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);

+ 2 - 1
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")

+ 16 - 0
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 = "_";
+
+}

+ 3 - 3
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;
 

+ 2 - 3
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<String, CryptoFolder> folders = WeakValuedCache.usingLoader(this::newFolder);
 	private final WeakValuedCache<String, CryptoFile> files = WeakValuedCache.usingLoader(this::newFile);
 	private final AtomicReference<String> 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);
 	}
 

+ 3 - 3
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<Cryptor> cryptorProvider;
 
 	@Inject

+ 10 - 0
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," //

+ 11 - 9
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();
 		}