Selaa lähdekoodia

unlock version 5 vaults

Sebastian Stenzel 9 vuotta sitten
vanhempi
commit
66faa13f40

+ 0 - 5
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/FileContentDecryptor.java

@@ -19,11 +19,6 @@ import javax.security.auth.Destroyable;
  */
 public interface FileContentDecryptor extends Destroyable, Closeable {
 
-	/**
-	 * @return Number of bytes of the decrypted file.
-	 */
-	long contentLength();
-
 	/**
 	 * Appends further ciphertext to this decryptor. This method might block until space becomes available. If so, it is interruptable.
 	 * 

+ 1 - 6
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/Constants.java

@@ -1,16 +1,11 @@
 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 Collection<Integer> SUPPORTED_VAULT_VERSIONS = Collections.unmodifiableCollection(Arrays.asList(3, 4));
-	static final Integer CURRENT_VAULT_VERSION = 4;
+	static final Integer CURRENT_VAULT_VERSION = 5;
 
 	public static final int PAYLOAD_SIZE = 32 * 1024;
 	public static final int NONCE_SIZE = 16;

+ 1 - 2
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/CryptorImpl.java

@@ -9,7 +9,6 @@
 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;
@@ -110,7 +109,7 @@ class CryptorImpl implements Cryptor {
 		assert keyFile != null;
 
 		// check version
-		if (!SUPPORTED_VAULT_VERSIONS.contains(keyFile.getVersion())) {
+		if (CURRENT_VAULT_VERSION != keyFile.getVersion()) {
 			throw new UnsupportedVaultFormatException(keyFile.getVersion(), CURRENT_VAULT_VERSION);
 		}
 

+ 1 - 16
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentDecryptorImpl.java

@@ -22,7 +22,6 @@ import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.concurrent.atomic.LongAdder;
 import java.util.function.Supplier;
 
 import javax.crypto.Cipher;
@@ -47,7 +46,6 @@ class FileContentDecryptorImpl implements FileContentDecryptor {
 	private final Supplier<Mac> hmacSha256;
 	private final FileHeader header;
 	private final boolean authenticate;
-	private final LongAdder cleartextBytesDecrypted = new LongAdder();
 	private ByteBuffer ciphertextBuffer = ByteBuffer.allocate(CHUNK_SIZE);
 	private long chunkNumber = 0;
 
@@ -58,11 +56,6 @@ class FileContentDecryptorImpl implements FileContentDecryptor {
 		this.chunkNumber = firstCiphertextByte / CHUNK_SIZE; // floor() by int-truncation
 	}
 
-	@Override
-	public long contentLength() {
-		return header.getPayload().getFilesize();
-	}
-
 	@Override
 	public void append(ByteBuffer ciphertext) throws InterruptedException {
 		if (ciphertext == FileContentCryptor.EOF) {
@@ -105,15 +98,7 @@ class FileContentDecryptorImpl implements FileContentDecryptor {
 	@Override
 	public ByteBuffer cleartext() throws InterruptedException {
 		try {
-			final ByteBuffer cleartext = dataProcessor.processedData();
-			long bytesUntilLogicalEof = contentLength() - cleartextBytesDecrypted.sum();
-			if (bytesUntilLogicalEof <= 0) {
-				return FileContentCryptor.EOF;
-			} else if (bytesUntilLogicalEof < cleartext.remaining()) {
-				cleartext.limit((int) bytesUntilLogicalEof);
-			}
-			cleartextBytesDecrypted.add(cleartext.remaining());
-			return cleartext;
+			return dataProcessor.processedData();
 		} catch (ExecutionException e) {
 			if (e.getCause() instanceof AuthenticationFailedException) {
 				throw new AuthenticationFailedException(e);

+ 1 - 17
main/filesystem-crypto/src/main/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImpl.java

@@ -36,8 +36,6 @@ import org.cryptomator.io.ByteBuffers;
 class FileContentEncryptorImpl implements FileContentEncryptor {
 
 	private static final String HMAC_SHA256 = "HmacSHA256";
-	private static final int PADDING_LOWER_BOUND = 4 * 1024; // 4k
-	private static final int PADDING_UPPER_BOUND = 16 * 1024 * 1024; // 16M
 	private static final int NUM_THREADS = Runtime.getRuntime().availableProcessors();
 	private static final int READ_AHEAD = 2;
 	private static final ExecutorService SHARED_DECRYPTION_EXECUTOR = Executors.newFixedThreadPool(NUM_THREADS);
@@ -63,7 +61,7 @@ class FileContentEncryptorImpl implements FileContentEncryptor {
 
 	@Override
 	public ByteBuffer getHeader() {
-		header.getPayload().setFilesize(cleartextBytesScheduledForEncryption.sum());
+		header.getPayload().setFilesize(-1l);
 		return header.toByteBuffer(headerKey, hmacSha256);
 	}
 
@@ -76,7 +74,6 @@ class FileContentEncryptorImpl implements FileContentEncryptor {
 	public void append(ByteBuffer cleartext) throws InterruptedException {
 		cleartextBytesScheduledForEncryption.add(cleartext.remaining());
 		if (cleartext == FileContentCryptor.EOF) {
-			appendSizeObfuscationPadding(cleartextBytesScheduledForEncryption.sum());
 			submitCleartextBuffer();
 			submitEof();
 		} else {
@@ -84,19 +81,6 @@ class FileContentEncryptorImpl implements FileContentEncryptor {
 		}
 	}
 
-	private void appendSizeObfuscationPadding(long actualSize) throws InterruptedException {
-		final int maxPaddingLength = (int) Math.min(Math.max(actualSize / 10, PADDING_LOWER_BOUND), PADDING_UPPER_BOUND); // preferably 10%, but at least lower bound and no more than upper bound
-		final int randomPaddingLength = randomSource.nextInt(maxPaddingLength);
-		final ByteBuffer buf = ByteBuffer.allocate(PAYLOAD_SIZE);
-		int remainingPadding = randomPaddingLength;
-		while (remainingPadding > 0) {
-			int bytesInCurrentIteration = Math.min(remainingPadding, PAYLOAD_SIZE);
-			buf.clear().limit(bytesInCurrentIteration);
-			appendAllAndSubmitIfFull(buf);
-			remainingPadding -= bytesInCurrentIteration;
-		}
-	}
-
 	private void appendAllAndSubmitIfFull(ByteBuffer cleartext) throws InterruptedException {
 		while (cleartext.hasRemaining()) {
 			ByteBuffers.copy(cleartext, cleartextBuffer);

+ 13 - 2
main/filesystem-crypto/src/main/java/org/cryptomator/filesystem/crypto/CryptoReadableFile.java

@@ -8,6 +8,9 @@
  *******************************************************************************/
 package org.cryptomator.filesystem.crypto;
 
+import static org.cryptomator.crypto.engine.impl.Constants.CHUNK_SIZE;
+import static org.cryptomator.crypto.engine.impl.Constants.PAYLOAD_SIZE;
+
 import java.io.IOException;
 import java.io.InterruptedIOException;
 import java.io.UncheckedIOException;
@@ -72,8 +75,16 @@ class CryptoReadableFile implements ReadableFile {
 
 	@Override
 	public long size() throws UncheckedIOException {
-		assert decryptor != null : "decryptor is always being set during position(long)";
-		return decryptor.contentLength();
+		long ciphertextSize = file.size() - cryptor.getHeaderSize();
+		long overheadPerChunk = CHUNK_SIZE - PAYLOAD_SIZE;
+		long numFullChunks = ciphertextSize / CHUNK_SIZE; // floor by int-truncation
+		long additionalCiphertextBytes = ciphertextSize % CHUNK_SIZE;
+		if (additionalCiphertextBytes > 0 && additionalCiphertextBytes <= overheadPerChunk) {
+			throw new IllegalArgumentException("Method not defined for input value " + ciphertextSize);
+		}
+		long additionalCleartextBytes = (additionalCiphertextBytes == 0) ? 0 : additionalCiphertextBytes - overheadPerChunk;
+		assert additionalCleartextBytes >= 0;
+		return PAYLOAD_SIZE * numFullChunks + additionalCleartextBytes;
 	}
 
 	@Override

+ 0 - 7
main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/NoFileContentCryptor.java

@@ -44,16 +44,9 @@ class NoFileContentCryptor implements FileContentCryptor {
 	private class Decryptor implements FileContentDecryptor {
 
 		private final BlockingQueue<Supplier<ByteBuffer>> cleartextQueue = new LinkedBlockingQueue<>();
-		private final long contentLength;
 
 		private Decryptor(ByteBuffer header) {
 			assert header.remaining() == Long.BYTES;
-			this.contentLength = header.getLong();
-		}
-
-		@Override
-		public long contentLength() {
-			return contentLength;
 		}
 
 		@Override

+ 6 - 7
main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/CryptorImplTest.java

@@ -21,7 +21,7 @@ public class CryptorImplTest {
 
 	@Test
 	public void testMasterkeyDecryptionWithCorrectPassphrase() throws IOException {
-		final String testMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
+		final String testMasterKey = "{\"version\":5,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
 				+ "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
 				+ "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
 				+ "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}";
@@ -31,7 +31,7 @@ public class CryptorImplTest {
 
 	@Test(expected = InvalidPassphraseException.class)
 	public void testMasterkeyDecryptionWithWrongPassphrase() throws IOException {
-		final String testMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
+		final String testMasterKey = "{\"version\":5,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
 				+ "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
 				+ "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
 				+ "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}";
@@ -52,7 +52,7 @@ public class CryptorImplTest {
 	@Ignore
 	@Test(expected = UnsupportedVaultFormatException.class)
 	public void testMasterkeyDecryptionWithMissingVersionMac() throws IOException {
-		final String testMasterKey = "{\"version\":3,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
+		final String testMasterKey = "{\"version\":5,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
 				+ "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
 				+ "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"}";
 		final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl();
@@ -62,7 +62,7 @@ public class CryptorImplTest {
 	@Ignore
 	@Test(expected = UnsupportedVaultFormatException.class)
 	public void testMasterkeyDecryptionWithWrongVersionMac() throws IOException {
-		final String testMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
+		final String testMasterKey = "{\"version\":5,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":2,\"scryptBlockSize\":8," //
 				+ "\"primaryMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
 				+ "\"hmacMasterKey\":\"mM+qoQ+o0qvPTiDAZYt+flaC3WbpNAx1sTXaUzxwpy0M9Ctj6Tih/Q==\"," //
 				+ "\"versionMac\":\"z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfoK=\"}";
@@ -72,14 +72,13 @@ public class CryptorImplTest {
 
 	@Test
 	public void testMasterkeyEncryption() throws IOException {
-		final String expectedMasterKey = "{\"version\":4,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":16384,\"scryptBlockSize\":8," //
+		final String expectedMasterKey = "{\"version\":5,\"scryptSalt\":\"AAAAAAAAAAA=\",\"scryptCostParam\":16384,\"scryptBlockSize\":8," //
 				+ "\"primaryMasterKey\":\"BJPIq5pvhN24iDtPJLMFPLaVJWdGog9k4n0P03j4ru+ivbWY9OaRGQ==\"," //
 				+ "\"hmacMasterKey\":\"BJPIq5pvhN24iDtPJLMFPLaVJWdGog9k4n0P03j4ru+ivbWY9OaRGQ==\"," //
-				+ "\"versionMac\":\"Z9J8Uc5K1f7YKckLUFpXG39NHK1qUjzadw5nvOqvfok=\"}";
+				+ "\"versionMac\":\"yuwoRE9GSdgQ2b//qRpTCj3W0qsVLxYVa7/KB3PkfA4=\"}";
 		final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl();
 		cryptor.randomizeMasterkey();
 		final byte[] masterkeyFile = cryptor.writeKeysToMasterkeyFile("asd");
-		System.out.println(new String(masterkeyFile));
 		Assert.assertArrayEquals(expectedMasterKey.getBytes(), masterkeyFile);
 	}
 

+ 0 - 39
main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentCryptorImplTest.java

@@ -137,45 +137,6 @@ public class FileContentCryptorImplTest {
 		Assert.assertArrayEquals("cleartext message".getBytes(), result);
 	}
 
-	@Test
-	public void testEncryptionAndDecryptionWithSizeObfuscationPadding() throws InterruptedException {
-		final byte[] keyBytes = new byte[32];
-		final SecretKey encryptionKey = new SecretKeySpec(keyBytes, "AES");
-		final SecretKey macKey = new SecretKeySpec(keyBytes, "HmacSHA256");
-		FileContentCryptor cryptor = new FileContentCryptorImpl(encryptionKey, macKey, RANDOM_MOCK_2);
-
-		ByteBuffer header = ByteBuffer.allocate(cryptor.getHeaderSize());
-		ByteBuffer ciphertext = ByteBuffer.allocate(16 + 11 + 500 + 32 + 1); // 16 bytes iv + 11 bytes ciphertext + 500 bytes padding + 32 bytes mac + 1.
-		try (FileContentEncryptor encryptor = cryptor.createFileContentEncryptor(Optional.empty(), 0)) {
-			encryptor.append(ByteBuffer.wrap("hello world".getBytes()));
-			encryptor.append(FileContentCryptor.EOF);
-			ByteBuffer buf;
-			while ((buf = encryptor.ciphertext()) != FileContentCryptor.EOF) {
-				ByteBuffers.copy(buf, ciphertext);
-			}
-			ByteBuffers.copy(encryptor.getHeader(), header);
-		}
-		header.flip();
-		ciphertext.flip();
-
-		Assert.assertEquals(16 + 11 + 500 + 32, ciphertext.remaining());
-
-		ByteBuffer plaintext = ByteBuffer.allocate(12); // 11 bytes plaintext + 1
-		try (FileContentDecryptor decryptor = cryptor.createFileContentDecryptor(header, 0, true)) {
-			decryptor.append(ciphertext);
-			decryptor.append(FileContentCryptor.EOF);
-			ByteBuffer buf;
-			while ((buf = decryptor.cleartext()) != FileContentCryptor.EOF) {
-				ByteBuffers.copy(buf, plaintext);
-			}
-		}
-		plaintext.flip();
-
-		byte[] result = new byte[plaintext.remaining()];
-		plaintext.get(result);
-		Assert.assertArrayEquals("hello world".getBytes(), result);
-	}
-
 	@Test(timeout = 20000) // assuming a minimum speed of 10mb/s during encryption and decryption 20s should be enough
 	public void testEncryptionAndDecryptionSpeed() throws InterruptedException, IOException {
 		final byte[] keyBytes = new byte[32];

+ 0 - 20
main/filesystem-crypto/src/test/java/org/cryptomator/crypto/engine/impl/FileContentEncryptorImplTest.java

@@ -95,24 +95,4 @@ public class FileContentEncryptorImplTest {
 		}
 	}
 
-	@Test
-	public void testSizeObfuscation() throws InterruptedException {
-		final byte[] keyBytes = new byte[32];
-		final SecretKey headerKey = new SecretKeySpec(keyBytes, "AES");
-		final SecretKey macKey = new SecretKeySpec(keyBytes, "AES");
-
-		try (FileContentEncryptor encryptor = new FileContentEncryptorImpl(headerKey, macKey, RANDOM_MOCK_2, 0)) {
-			encryptor.append(FileContentCryptor.EOF);
-
-			ByteBuffer result = ByteBuffer.allocate(91); // 16 bytes iv + 42 bytes size obfuscation + 32 bytes mac + 1
-			ByteBuffer buf;
-			while ((buf = encryptor.ciphertext()) != FileContentCryptor.EOF) {
-				ByteBuffers.copy(buf, result);
-			}
-			result.flip();
-
-			Assert.assertEquals(90, result.remaining());
-		}
-	}
-
 }