diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java
index 3b627fb59..3178e4f14 100644
--- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java
+++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java
@@ -50,6 +50,7 @@ import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.crypto.exceptions.EncryptFailedException;
import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
import org.cryptomator.crypto.exceptions.WrongPasswordException;
import org.cryptomator.crypto.io.SeekableByteChannelInputStream;
import org.cryptomator.crypto.io.SeekableByteChannelOutputStream;
@@ -133,6 +134,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
// save encrypted masterkey:
final KeyFile keyfile = new KeyFile();
+ keyfile.setVersion(KeyFile.CURRENT_VERSION);
keyfile.setScryptSalt(kekSalt);
keyfile.setScryptCostParam(SCRYPT_COST_PARAM);
keyfile.setScryptBlockSize(SCRYPT_BLOCK_SIZE);
@@ -151,13 +153,19 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
* @throws DecryptFailedException If the decryption failed for various reasons (including wrong password).
* @throws WrongPasswordException If the provided password was wrong. Note: Sometimes the algorithm itself fails due to a wrong password. In this case a DecryptFailedException will be thrown.
* @throws UnsupportedKeyLengthException If the masterkey has been encrypted with a higher key length than supported by the system. In this case Java JCE needs to be installed.
+ * @throws UnsupportedVaultException If the masterkey file is too old or too modern.
*/
@Override
- public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException {
+ public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException, UnsupportedVaultException {
try {
// load encrypted masterkey:
final KeyFile keyfile = objectMapper.readValue(in, KeyFile.class);
+ // check version
+ if (keyfile.getVersion() != KeyFile.CURRENT_VERSION) {
+ throw new UnsupportedVaultException(keyfile.getVersion(), KeyFile.CURRENT_VERSION);
+ }
+
// check, whether the key length is supported:
final int maxKeyLen = Cipher.getMaxAllowedKeyLength(AES_KEY_ALGORITHM);
if (keyfile.getKeyLength() > maxKeyLen) {
@@ -225,17 +233,16 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
}
}
- private Cipher aesEcbCipher(SecretKey key, int cipherMode) {
+ private Cipher aesCbcCipher(SecretKey key, byte[] iv, int cipherMode) {
try {
- final Cipher cipher = Cipher.getInstance(AES_ECB_CIPHER);
- cipher.init(cipherMode, key);
+ final Cipher cipher = Cipher.getInstance(AES_CBC_CIPHER);
+ cipher.init(cipherMode, key, new IvParameterSpec(iv));
return cipher;
} catch (InvalidKeyException ex) {
throw new IllegalArgumentException("Invalid key.", ex);
- } catch (NoSuchAlgorithmException | NoSuchPaddingException ex) {
- throw new AssertionError("Every implementation of the Java platform is required to support AES/ECB/PKCS5Padding.", ex);
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException ex) {
+ throw new AssertionError("Every implementation of the Java platform is required to support AES/CBC/PKCS5Padding, which accepts an IV", ex);
}
-
}
private Mac hmacSha256(SecretKey key) {
@@ -291,7 +298,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
/**
* Each path component, i.e. file or directory name separated by path separators, gets encrypted for its own.
- * Encryption will blow up the filename length due to aes block sizes and base32 encoding. The result may be too long for some old file systems.
+ * Encryption will blow up the filename length due to aes block sizes, IVs and base32 encoding. The result may be too long for some old file systems.
* This means that we need a workaround for filenames longer than the limit defined in {@link FileNamingConventions#ENCRYPTED_FILENAME_LENGTH_LIMIT}.
*
* In any case we will create the encrypted filename normally. For those, that are too long, we calculate a checksum. No cryptographically secure hash is needed here. We just want an uniform
@@ -364,15 +371,20 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
return null;
}
+ // read iv:
+ final byte[] iv = new byte[AES_BLOCK_LENGTH];
+ headerBuf.position(0);
+ headerBuf.get(iv);
+
// read content length:
- headerBuf.position(16);
final byte[] encryptedContentLengthBytes = new byte[AES_BLOCK_LENGTH];
+ headerBuf.position(16);
headerBuf.get(encryptedContentLengthBytes);
- final Long fileSize = decryptContentLength(encryptedContentLengthBytes);
+ final Long fileSize = decryptContentLength(encryptedContentLengthBytes, iv);
// read stored header mac:
- headerBuf.position(32);
final byte[] storedHeaderMac = new byte[32];
+ headerBuf.position(32);
headerBuf.get(storedHeaderMac);
// calculate mac over first 32 bytes of header:
@@ -389,9 +401,9 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
return fileSize;
}
- private long decryptContentLength(byte[] encryptedContentLengthBytes) {
+ private long decryptContentLength(byte[] encryptedContentLengthBytes, byte[] iv) {
try {
- final Cipher sizeCipher = aesEcbCipher(primaryMasterKey, Cipher.DECRYPT_MODE);
+ final Cipher sizeCipher = aesCbcCipher(primaryMasterKey, iv, Cipher.DECRYPT_MODE);
final byte[] decryptedFileSize = sizeCipher.doFinal(encryptedContentLengthBytes);
final ByteBuffer fileSizeBuffer = ByteBuffer.wrap(decryptedFileSize);
return fileSizeBuffer.getLong();
@@ -400,11 +412,11 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
}
}
- private byte[] encryptContentLength(long contentLength) {
+ private byte[] encryptContentLength(long contentLength, byte[] iv) {
try {
final ByteBuffer fileSizeBuffer = ByteBuffer.allocate(Long.BYTES);
fileSizeBuffer.putLong(contentLength);
- final Cipher sizeCipher = aesEcbCipher(primaryMasterKey, Cipher.ENCRYPT_MODE);
+ final Cipher sizeCipher = aesCbcCipher(primaryMasterKey, iv, Cipher.ENCRYPT_MODE);
return sizeCipher.doFinal(fileSizeBuffer.array());
} catch (IllegalBlockSizeException | BadPaddingException e) {
throw new IllegalStateException("Block size must be valid, as padding is requested. BadPaddingException not possible in encrypt mode.", e);
@@ -414,7 +426,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
@Override
public boolean isAuthentic(SeekableByteChannel encryptedFile) throws IOException {
// read header:
- encryptedFile.position(0);
+ encryptedFile.position(0l);
final ByteBuffer headerBuf = ByteBuffer.allocate(96);
final int headerBytesRead = encryptedFile.read(headerBuf);
if (headerBytesRead != headerBuf.capacity()) {
@@ -422,23 +434,23 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
}
// read header mac:
- headerBuf.position(32);
final byte[] storedHeaderMac = new byte[32];
+ headerBuf.position(32);
headerBuf.get(storedHeaderMac);
// read content mac:
- headerBuf.position(64);
final byte[] storedContentMac = new byte[32];
+ headerBuf.position(64);
headerBuf.get(storedContentMac);
// calculate mac over first 32 bytes of header:
final Mac headerMac = this.hmacSha256(hMacMasterKey);
- headerBuf.rewind();
+ headerBuf.position(0);
headerBuf.limit(32);
headerMac.update(headerBuf);
// calculate mac over content:
- encryptedFile.position(96);
+ encryptedFile.position(96l);
final Mac contentMac = this.hmacSha256(hMacMasterKey);
final InputStream in = new SeekableByteChannelInputStream(encryptedFile);
final InputStream macIn = new MacInputStream(in, contentMac);
@@ -453,29 +465,32 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
@Override
public Long decryptFile(SeekableByteChannel encryptedFile, OutputStream plaintextFile) throws IOException, DecryptFailedException {
// read header:
- encryptedFile.position(0);
+ encryptedFile.position(0l);
final ByteBuffer headerBuf = ByteBuffer.allocate(96);
final int headerBytesRead = encryptedFile.read(headerBuf);
if (headerBytesRead != headerBuf.capacity()) {
throw new IOException("Failed to read file header.");
}
- headerBuf.rewind();
// read iv:
final byte[] iv = new byte[AES_BLOCK_LENGTH];
+ headerBuf.position(0);
headerBuf.get(iv);
// read content length:
final byte[] encryptedContentLengthBytes = new byte[AES_BLOCK_LENGTH];
+ headerBuf.position(16);
headerBuf.get(encryptedContentLengthBytes);
- final Long fileSize = decryptContentLength(encryptedContentLengthBytes);
+ final Long fileSize = decryptContentLength(encryptedContentLengthBytes, iv);
// read header mac:
final byte[] headerMac = new byte[32];
+ headerBuf.position(32);
headerBuf.get(headerMac);
// read content mac:
final byte[] contentMac = new byte[32];
+ headerBuf.position(64);
headerBuf.get(contentMac);
// decrypt content
@@ -507,7 +522,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
@Override
public Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plaintextFile, long pos, long length) throws IOException, DecryptFailedException {
// read iv:
- encryptedFile.position(0);
+ encryptedFile.position(0l);
final ByteBuffer countingIv = ByteBuffer.allocate(AES_BLOCK_LENGTH);
final int numIvBytesRead = encryptedFile.read(countingIv);
@@ -540,23 +555,24 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
@Override
public Long encryptFile(InputStream plaintextFile, SeekableByteChannel encryptedFile) throws IOException, EncryptFailedException {
// truncate file
- encryptedFile.truncate(0);
+ encryptedFile.truncate(0l);
// use an IV, whose last 8 bytes store a long used in counter mode and write initial value to file.
- final ByteBuffer iv = ByteBuffer.wrap(randomData(AES_BLOCK_LENGTH));
- iv.putInt(AES_BLOCK_LENGTH - Integer.BYTES, 0);
+ final ByteBuffer ivBuf = ByteBuffer.wrap(randomData(AES_BLOCK_LENGTH));
+ ivBuf.putInt(AES_BLOCK_LENGTH - Integer.BYTES, 0);
+ final byte[] iv = ivBuf.array();
// 96 byte header buffer (16 IV, 16 size, 32 headerMac, 32 contentMac)
// prefilled with "zero" content length for impatient processes, which want to know the size, before file has been completely written:
final ByteBuffer headerBuf = ByteBuffer.allocate(96);
headerBuf.position(16);
- headerBuf.put(encryptContentLength(0l));
+ headerBuf.put(encryptContentLength(0l, iv));
headerBuf.flip();
headerBuf.limit(96);
encryptedFile.write(headerBuf);
// content encryption:
- final Cipher cipher = this.aesCtrCipher(primaryMasterKey, iv.array(), Cipher.ENCRYPT_MODE);
+ final Cipher cipher = this.aesCtrCipher(primaryMasterKey, iv, Cipher.ENCRYPT_MODE);
final Mac contentMac = this.hmacSha256(hMacMasterKey);
final OutputStream out = new SeekableByteChannelOutputStream(encryptedFile);
final OutputStream macOut = new MacOutputStream(out, contentMac);
@@ -586,7 +602,7 @@ public class Aes256Cryptor implements Cryptor, AesCryptographicConfiguration, Fi
// create and write header:
headerBuf.clear();
headerBuf.put(iv);
- headerBuf.put(encryptContentLength(plaintextSize));
+ headerBuf.put(encryptContentLength(plaintextSize, iv));
headerBuf.flip();
final Mac headerMac = this.hmacSha256(hMacMasterKey);
headerMac.update(headerBuf);
diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java
index 4bd81f93f..852248b9b 100644
--- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java
+++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java
@@ -26,14 +26,14 @@ interface AesCryptographicConfiguration {
int SCRYPT_BLOCK_SIZE = 8;
/**
- * Number of bytes of the master key. Should be the maximum possible AES key length to provide best security.
+ * Preferred number of bytes of the master key.
*/
int PREF_MASTER_KEY_LENGTH_IN_BITS = 256;
/**
* Number of bytes used as seed for the PRNG.
*/
- int PRNG_SEED_LENGTH = 32;
+ int PRNG_SEED_LENGTH = 16;
/**
* Algorithm used for random number generation.
@@ -60,30 +60,22 @@ interface AesCryptographicConfiguration {
String AES_KEYWRAP_CIPHER = "AESWrap";
/**
- * Cipher specs for file name and file content encryption. Using CTR-mode for random access.
- * Important: As JCE doesn't support a padding, input must be a multiple of the block size.
+ * Cipher specs for file content encryption. Using CTR-mode for random access.
*
* @see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Cipher
*/
String AES_CTR_CIPHER = "AES/CTR/NoPadding";
/**
- * Cipher specs for single block encryption (like file size).
+ * Cipher specs for file header encryption (fixed-length block cipher).
*
* @see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#impl
*/
- String AES_ECB_CIPHER = "AES/ECB/PKCS5Padding";
+ String AES_CBC_CIPHER = "AES/CBC/PKCS5Padding";
/**
* AES block size is 128 bit or 16 bytes.
*/
int AES_BLOCK_LENGTH = 16;
- /**
- * Number of non-zero bytes in the IV used for file name encryption. Less means shorter encrypted filenames, more means higher entropy.
- * Maximum length is {@value #AES_BLOCK_LENGTH}. Even the shortest base32 (see {@link FileNamingConventions#ENCRYPTED_FILENAME_CODEC})
- * encoded byte array will need 8 chars. The maximum number of bytes that fit in 8 base32 chars is 5. Thus 5 is the ideal length.
- */
- int FILE_NAME_IV_LENGTH = 5;
-
}
diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/FileNamingConventions.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/FileNamingConventions.java
index deb00f70a..9a6f4b771 100644
--- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/FileNamingConventions.java
+++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/FileNamingConventions.java
@@ -22,7 +22,7 @@ interface FileNamingConventions {
BaseNCodec ENCRYPTED_FILENAME_CODEC = new Base32();
/**
- * Maximum length possible on file systems with a filename limit of 255 chars.
+ * Maximum length possible on file systems with a filename or even path length limit of 255 chars.
* Also we would need a few chars for our file extension, so lets use {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT}.
*/
int ENCRYPTED_FILENAME_LENGTH_LIMIT = 128;
diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/KeyFile.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/KeyFile.java
index 330730d6f..dec5d477a 100644
--- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/KeyFile.java
+++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/KeyFile.java
@@ -4,10 +4,13 @@ import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
-@JsonPropertyOrder(value = {"scryptSalt", "scryptCostParam", "scryptBlockSize", "keyLength", "primaryMasterKey", "hMacMasterKey"})
+@JsonPropertyOrder(value = {"version", "scryptSalt", "scryptCostParam", "scryptBlockSize", "keyLength", "primaryMasterKey", "hMacMasterKey"})
public class KeyFile implements Serializable {
+ static final Integer CURRENT_VERSION = 1;
private static final long serialVersionUID = 8578363158959619885L;
+
+ private Integer version;
private byte[] scryptSalt;
private int scryptCostParam;
private int scryptBlockSize;
@@ -15,6 +18,14 @@ public class KeyFile implements Serializable {
private byte[] primaryMasterKey;
private byte[] hMacMasterKey;
+ public Integer getVersion() {
+ return version;
+ }
+
+ public void setVersion(Integer version) {
+ this.version = version;
+ }
+
public byte[] getScryptSalt() {
return scryptSalt;
}
diff --git a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java
index 39fc568f8..a04303fb2 100644
--- a/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java
+++ b/main/crypto-aes/src/test/java/org/cryptomator/crypto/aes256/Aes256CryptorTest.java
@@ -25,6 +25,7 @@ import org.cryptomator.crypto.CryptorMetadataSupport;
import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.crypto.exceptions.EncryptFailedException;
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
import org.cryptomator.crypto.exceptions.WrongPasswordException;
import org.junit.Assert;
import org.junit.Test;
@@ -32,7 +33,7 @@ import org.junit.Test;
public class Aes256CryptorTest {
@Test
- public void testCorrectPassword() throws IOException, WrongPasswordException, DecryptFailedException, UnsupportedKeyLengthException, DestroyFailedException {
+ public void testCorrectPassword() throws IOException, WrongPasswordException, DecryptFailedException, UnsupportedKeyLengthException, DestroyFailedException, UnsupportedVaultException {
final String pw = "asd";
final Aes256Cryptor cryptor = new Aes256Cryptor();
final ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -48,7 +49,7 @@ public class Aes256CryptorTest {
}
@Test
- public void testWrongPassword() throws IOException, DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, DestroyFailedException {
+ public void testWrongPassword() throws IOException, DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, DestroyFailedException, UnsupportedVaultException {
final String pw = "asd";
final Aes256Cryptor cryptor = new Aes256Cryptor();
final ByteArrayOutputStream out = new ByteArrayOutputStream();
diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptorDecorator.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptorDecorator.java
index 56eea4313..ab44886f0 100644
--- a/main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptorDecorator.java
+++ b/main/crypto-api/src/main/java/org/cryptomator/crypto/AbstractCryptorDecorator.java
@@ -13,6 +13,7 @@ import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.crypto.exceptions.EncryptFailedException;
import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
import org.cryptomator.crypto.exceptions.WrongPasswordException;
public class AbstractCryptorDecorator implements Cryptor {
@@ -29,7 +30,7 @@ public class AbstractCryptorDecorator implements Cryptor {
}
@Override
- public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException {
+ public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException, UnsupportedVaultException {
cryptor.decryptMasterKey(in, password);
}
diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java
index 1eecf0f21..bf7a5f8ba 100644
--- a/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java
+++ b/main/crypto-api/src/main/java/org/cryptomator/crypto/Cryptor.java
@@ -21,6 +21,7 @@ import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.crypto.exceptions.EncryptFailedException;
import org.cryptomator.crypto.exceptions.MacAuthenticationFailedException;
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
import org.cryptomator.crypto.exceptions.WrongPasswordException;
/**
@@ -39,8 +40,9 @@ public interface Cryptor extends Destroyable {
* @throws DecryptFailedException If the decryption failed for various reasons (including wrong password).
* @throws WrongPasswordException If the provided password was wrong. Note: Sometimes the algorithm itself fails due to a wrong password. In this case a DecryptFailedException will be thrown.
* @throws UnsupportedKeyLengthException If the masterkey has been encrypted with a higher key length than supported by the system. In this case Java JCE needs to be installed.
+ * @throws UnsupportedVaultException If the masterkey file is too old or too modern.
*/
- void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException;
+ void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException, UnsupportedVaultException;
/**
* Encrypts a given plaintext path representing a directory structure. See {@link #encryptFilename(String, CryptorMetadataSupport)} for contents inside directories.
diff --git a/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedVaultException.java b/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedVaultException.java
new file mode 100644
index 000000000..b2a69ac54
--- /dev/null
+++ b/main/crypto-api/src/main/java/org/cryptomator/crypto/exceptions/UnsupportedVaultException.java
@@ -0,0 +1,32 @@
+package org.cryptomator.crypto.exceptions;
+
+public class UnsupportedVaultException extends Exception {
+
+ private static final long serialVersionUID = -5147549533387945622L;
+
+ private final Integer detectedVersion;
+ private final Integer supportedVersion;
+
+ public UnsupportedVaultException(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/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
index 91fa9df66..7215b39a8 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
@@ -20,6 +20,7 @@ import javafx.scene.control.Label;
import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
import org.cryptomator.crypto.exceptions.WrongPasswordException;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.Vault;
@@ -108,6 +109,14 @@ public class ChangePasswordController implements Initializable {
newPasswordField.swipe();
retypePasswordField.swipe();
return;
+ } catch (UnsupportedVaultException e) {
+ if (e.isVaultOlderThanSoftware()) {
+ messageLabel.setText(rb.getString("changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware"));
+ } else if (e.isSoftwareOlderThanVault()) {
+ messageLabel.setText(rb.getString("changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault"));
+ }
+ newPasswordField.swipe();
+ retypePasswordField.swipe();
} finally {
oldPasswordField.swipe();
}
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 877d18a39..6aab80736 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
@@ -35,6 +35,7 @@ import javax.security.auth.DestroyFailedException;
import org.apache.commons.lang3.CharUtils;
import org.cryptomator.crypto.exceptions.DecryptFailedException;
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
+import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
import org.cryptomator.crypto.exceptions.WrongPasswordException;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.Vault;
@@ -131,6 +132,14 @@ public class UnlockController implements Initializable {
progressIndicator.setVisible(false);
messageLabel.setText(rb.getString("unlock.errorMessage.unsupportedKeyLengthInstallJCE"));
LOG.warn("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
+ } catch (UnsupportedVaultException e) {
+ setControlsDisabled(false);
+ progressIndicator.setVisible(false);
+ if (e.isVaultOlderThanSoftware()) {
+ messageLabel.setText(rb.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware"));
+ } else if (e.isSoftwareOlderThanVault()) {
+ messageLabel.setText(rb.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault"));
+ }
} catch (DestroyFailedException e) {
setControlsDisabled(false);
progressIndicator.setVisible(false);
diff --git a/main/ui/src/main/resources/localization.properties b/main/ui/src/main/resources/localization.properties
index 11b6c5abd..03fc09d2a 100644
--- a/main/ui/src/main/resources/localization.properties
+++ b/main/ui/src/main/resources/localization.properties
@@ -32,6 +32,8 @@ unlock.button.unlock=Unlock vault
unlock.errorMessage.wrongPassword=Wrong password.
unlock.errorMessage.decryptionFailed=Decryption failed.
unlock.errorMessage.unsupportedKeyLengthInstallJCE=Decryption failed. Please install Oracle JCE Unlimited Strength Policy.
+unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.
+unlock.errorMessage.unsupportedVersion.softwareOlderThanVault=Unsupported vault. This vault has been created with a newer version of Cryptomator.
unlock.messageLabel.startServerFailed=Starting WebDAV server failed.
# change_password.fxml
@@ -42,6 +44,8 @@ changePassword.button.unlock=Change password
changePassword.errorMessage.wrongPassword=Wrong password.
changePassword.errorMessage.decryptionFailed=Decryption failed.
changePassword.errorMessage.unsupportedKeyLengthInstallJCE=Decryption failed. Please install Oracle JCE Unlimited Strength Policy.
+changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware=Unsupported vault. This vault has been created with an older version of Cryptomator.
+changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault=Unsupported vault. This vault has been created with a newer version of Cryptomator.
changePassword.infoMessage.success=Password changed.
# unlocked.fxml