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 2c1bebc11..8d485c15c 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 @@ -11,17 +11,21 @@ package org.cryptomator.crypto.engine.impl; import static org.cryptomator.crypto.engine.impl.Constants.CURRENT_VAULT_VERSION; import java.io.IOException; +import java.nio.ByteBuffer; import java.security.InvalidKeyException; +import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; +import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import javax.security.auth.DestroyFailedException; import javax.security.auth.Destroyable; +import org.apache.commons.lang3.ArrayUtils; import org.cryptomator.common.LazyInitializer; import org.cryptomator.crypto.engine.Cryptor; import org.cryptomator.crypto.engine.FileContentCryptor; @@ -100,15 +104,21 @@ class CryptorImpl implements Cryptor { } // check version - if (keyFile.getVersion() != CURRENT_VAULT_VERSION) { + if (keyFile.getVersion() != CURRENT_VAULT_VERSION || ArrayUtils.isEmpty(keyFile.getVersionMac())) { throw new UnsupportedVaultFormatException(keyFile.getVersion(), CURRENT_VAULT_VERSION); } final byte[] kekBytes = Scrypt.scrypt(passphrase, keyFile.getScryptSalt(), keyFile.getScryptCostParam(), keyFile.getScryptBlockSize(), KEYLENGTH_IN_BYTES); try { final SecretKey kek = new SecretKeySpec(kekBytes, ENCRYPTION_ALG); - this.encryptionKey = AesKeyWrap.unwrap(kek, keyFile.getEncryptionMasterKey(), ENCRYPTION_ALG); this.macKey = AesKeyWrap.unwrap(kek, keyFile.getMacMasterKey(), MAC_ALG); + final Mac mac = new ThreadLocalMac(macKey, MAC_ALG).get(); + final byte[] versionMac = mac.doFinal(ByteBuffer.allocate(Integer.BYTES).putInt(CURRENT_VAULT_VERSION).array()); + if (!MessageDigest.isEqual(versionMac, keyFile.getVersionMac())) { + destroyQuietly(macKey); + throw new UnsupportedVaultFormatException(Integer.MAX_VALUE, CURRENT_VAULT_VERSION); + } + this.encryptionKey = AesKeyWrap.unwrap(kek, keyFile.getEncryptionMasterKey(), ENCRYPTION_ALG); } catch (InvalidKeyException e) { throw new InvalidPassphraseException(); } catch (NoSuchAlgorithmException e) { @@ -134,6 +144,9 @@ class CryptorImpl implements Cryptor { Arrays.fill(kekBytes, (byte) 0x00); } + final Mac mac = new ThreadLocalMac(macKey, MAC_ALG).get(); + final byte[] versionMac = mac.doFinal(ByteBuffer.allocate(Integer.BYTES).putInt(CURRENT_VAULT_VERSION).array()); + final KeyFile keyfile = new KeyFile(); keyfile.setVersion(CURRENT_VAULT_VERSION); keyfile.setScryptSalt(scryptSalt); @@ -141,6 +154,7 @@ class CryptorImpl implements Cryptor { keyfile.setScryptBlockSize(SCRYPT_BLOCK_SIZE); keyfile.setEncryptionMasterKey(wrappedEncryptionKey); keyfile.setMacMasterKey(wrappedMacKey); + keyfile.setVersionMac(versionMac); try { final ObjectMapper om = new ObjectMapper(); 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 829f7ed39..ee833c215 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 @@ -15,7 +15,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; @JsonIgnoreProperties(ignoreUnknown = true) -@JsonPropertyOrder(value = {"version", "scryptSalt", "scryptCostParam", "scryptBlockSize", "primaryMasterKey", "hmacMasterKey"}) +@JsonPropertyOrder(value = {"version", "scryptSalt", "scryptCostParam", "scryptBlockSize", "primaryMasterKey", "hmacMasterKey", "versionMac"}) class KeyFile implements Serializable { private static final long serialVersionUID = 8578363158959619885L; @@ -38,6 +38,9 @@ class KeyFile implements Serializable { @JsonProperty("hmacMasterKey") private byte[] macMasterKey; + @JsonProperty("versionMac") + private byte[] versionMac; + public Integer getVersion() { return version; } @@ -86,4 +89,12 @@ class KeyFile implements Serializable { this.macMasterKey = macMasterKey; } + public byte[] getVersionMac() { + return versionMac; + } + + public void setVersionMac(byte[] versionMac) { + this.versionMac = versionMac; + } + } \ No newline at end of file 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 460976134..3320f8398 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 @@ -22,7 +22,8 @@ public class CryptorImplTest { public void testMasterkeyDecryptionWithCorrectPassphrase() throws IOException { final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // + "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // - + "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"}"; + + "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // + + "\"versionMac\":\"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLA=\"}"; final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd"); } @@ -31,7 +32,8 @@ public class CryptorImplTest { public void testMasterkeyDecryptionWithWrongPassphrase() throws IOException { final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // + "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // - + "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"}"; + + "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // + + "\"versionMac\":\"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLA=\"}"; final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "qwe"); } @@ -39,17 +41,38 @@ public class CryptorImplTest { @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==\"," // + + "\"versionMac\":\"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLA=\"}"; + final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); + cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd"); + } + + @Test(expected = UnsupportedVaultFormatException.class) + public void testMasterkeyDecryptionWithMissingVersionMac() throws IOException { + final String testMasterKey = "{\"version\":3,\"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"); + cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd"); + } + + @Test(expected = UnsupportedVaultFormatException.class) + public void testMasterkeyDecryptionWithWrongVersionMac() throws IOException { + final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," // + + "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // + + "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," // + + "\"versionMac\":\"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLa=\"}"; + 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," // + "\"primaryMasterKey\":\"BJPIq5pvhN24iDtPJLMFPLaVJWdGog9k4n0P03j4ru+ivbWY9OaRGQ==\"," // - + "\"hmacMasterKey\":\"BJPIq5pvhN24iDtPJLMFPLaVJWdGog9k4n0P03j4ru+ivbWY9OaRGQ==\"}"; + + "\"hmacMasterKey\":\"BJPIq5pvhN24iDtPJLMFPLaVJWdGog9k4n0P03j4ru+ivbWY9OaRGQ==\"," // + + "\"versionMac\":\"iUmRRHITuyJsJbVNqGNw+82YQ4A3Rma7j/y1v0DCVLA=\"}"; final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl(); cryptor.randomizeMasterkey(); final byte[] masterkeyFile = cryptor.writeKeysToMasterkeyFile("asd");