mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-04-19 17:16:53 -04:00
Merge branch 'develop' into feature/external-keychain
# Conflicts: # main/pom.xml # main/ui/src/main/java/org/cryptomator/ui/CryptomatorModule.java
This commit is contained in:
@@ -3,6 +3,9 @@ sudo: required
|
||||
dist: trusty
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
env:
|
||||
global:
|
||||
- secure: "IfYURwZaDWuBDvyn47n0k1Zod/IQw1FF+CS5nnV08Q+NfC3vGGJMwV8m59XnbfwnWGxwvCaAbk4qP6s6+ijgZNKkvgfFMo3rfTok5zt43bIqgaFOANYV+OC/1c59gYD6ZUxhW5iNgMgU3qdsRtJuwSmfkVv/jKyLGfAbS4kN8BA=" #coverity
|
||||
|
||||
@@ -17,6 +17,13 @@ public interface File extends Node, Comparable<File> {
|
||||
|
||||
static final int EOF = -1;
|
||||
|
||||
/**
|
||||
* @return The current size of the file. This value is a snapshot and might have been changed by concurrent modifications.
|
||||
* @throws UncheckedIOException
|
||||
* if an {@link IOException} occurs
|
||||
*/
|
||||
long size() throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Opens this file for reading.
|
||||
@@ -39,7 +46,6 @@ public interface File extends Node, Comparable<File> {
|
||||
* if an {@link IOException} occurs while opening the file, the
|
||||
* file does not exist or is a directory
|
||||
*/
|
||||
|
||||
ReadableFile openReadable() throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,13 +30,6 @@ public interface ReadableFile extends ReadableByteChannel {
|
||||
@Override
|
||||
int read(ByteBuffer target) throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* @return The current size of the file. This value is a snapshot and might have been changed by concurrent modifications.
|
||||
* @throws UncheckedIOException
|
||||
* if an {@link IOException} occurs
|
||||
*/
|
||||
long size() throws UncheckedIOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Fast-forwards or rewinds the file to the specified position.
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
|
||||
public abstract class DelegatingFile<D extends DelegatingFolder<D, ?>> extends DelegatingNode<File>implements File {
|
||||
public abstract class DelegatingFile<D extends DelegatingFolder<D, ?>> extends DelegatingNode<File> implements File {
|
||||
|
||||
private final D parent;
|
||||
|
||||
@@ -29,6 +29,11 @@ public abstract class DelegatingFile<D extends DelegatingFolder<D, ?>> extends D
|
||||
return Optional.of(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws UncheckedIOException {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadableFile openReadable() throws UncheckedIOException {
|
||||
return delegate.openReadable();
|
||||
|
||||
@@ -31,11 +31,6 @@ public class DelegatingReadableFile implements ReadableFile {
|
||||
return delegate.read(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws UncheckedIOException {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void position(long position) throws UncheckedIOException {
|
||||
delegate.position(position);
|
||||
|
||||
@@ -30,6 +30,16 @@ public class DelegatingFileTest {
|
||||
Assert.assertEquals(mockFile.name(), delegatingFile.name());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSize() {
|
||||
File mockFile = Mockito.mock(File.class);
|
||||
DelegatingFile<?> delegatingFile = new TestDelegatingFile(null, mockFile);
|
||||
|
||||
Mockito.when(mockFile.size()).thenReturn(42l);
|
||||
Assert.assertEquals(42l, delegatingFile.size());
|
||||
Mockito.verify(mockFile).size();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParent() {
|
||||
Folder mockFolder = Mockito.mock(Folder.class);
|
||||
|
||||
@@ -42,17 +42,6 @@ public class DelegatingReadableFileTest {
|
||||
Mockito.verify(mockReadableFile).read(buf);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSize() {
|
||||
ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class);
|
||||
@SuppressWarnings("resource")
|
||||
DelegatingReadableFile delegatingReadableFile = new DelegatingReadableFile(mockReadableFile);
|
||||
|
||||
Mockito.when(mockReadableFile.size()).thenReturn(42l);
|
||||
Assert.assertEquals(42l, delegatingReadableFile.size());
|
||||
Mockito.verify(mockReadableFile).size();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPosition() {
|
||||
ReadableFile mockReadableFile = Mockito.mock(ReadableFile.class);
|
||||
|
||||
@@ -130,7 +130,7 @@ public class CryptoFileSystemIntegrationTest {
|
||||
|
||||
// toggle last bit
|
||||
try (WritableFile writable = physicalFile.openWritable(); ReadableFile readable = physicalFile.openReadable()) {
|
||||
ByteBuffer buf = ByteBuffer.allocate((int) readable.size());
|
||||
ByteBuffer buf = ByteBuffer.allocate((int) physicalFile.size());
|
||||
readable.read(buf);
|
||||
buf.array()[buf.limit() - 1] ^= 0x01;
|
||||
buf.flip();
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -100,11 +100,6 @@ class BlockAlignedReadableFile implements ReadableFile {
|
||||
return delegate.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws UncheckedIOException {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws UncheckedIOException {
|
||||
delegate.close();
|
||||
|
||||
@@ -79,10 +79,10 @@ final class ConflictResolver {
|
||||
}
|
||||
|
||||
private boolean isSameFileBasedOnSample(File file1, File file2, int sampleSize) {
|
||||
try (ReadableFile r1 = file1.openReadable(); ReadableFile r2 = file2.openReadable()) {
|
||||
if (r1.size() != r2.size()) {
|
||||
return false;
|
||||
} else {
|
||||
if (file1.size() != file2.size()) {
|
||||
return false;
|
||||
} else {
|
||||
try (ReadableFile r1 = file1.openReadable(); ReadableFile r2 = file2.openReadable()) {
|
||||
ByteBuffer beginOfFile1 = ByteBuffer.allocate(sampleSize);
|
||||
ByteBuffer beginOfFile2 = ByteBuffer.allocate(sampleSize);
|
||||
int bytesRead1 = r1.read(beginOfFile1);
|
||||
|
||||
@@ -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.UncheckedIOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.util.Optional;
|
||||
@@ -28,6 +31,25 @@ class CryptoFile extends CryptoNode implements File {
|
||||
return parent().get().encryptChildName(name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws UncheckedIOException {
|
||||
if (!physicalFile().isPresent()) {
|
||||
return -1l;
|
||||
} else {
|
||||
File file = physicalFile().get();
|
||||
long ciphertextSize = file.size() - cryptor.getFileContentCryptor().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
|
||||
public ReadableFile openReadable() {
|
||||
boolean authenticate = !fileSystem().delegate().shouldSkipAuthentication(toString());
|
||||
|
||||
@@ -70,12 +70,6 @@ class CryptoReadableFile implements ReadableFile {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws UncheckedIOException {
|
||||
assert decryptor != null : "decryptor is always being set during position(long)";
|
||||
return decryptor.contentLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void position(long position) throws UncheckedIOException {
|
||||
if (readAheadTask != null) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,20 +43,6 @@ public class FileContentCryptorImplTest {
|
||||
|
||||
};
|
||||
|
||||
private static final SecureRandom RANDOM_MOCK_2 = new SecureRandom() {
|
||||
|
||||
@Override
|
||||
public int nextInt(int bound) {
|
||||
return 500;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nextBytes(byte[] bytes) {
|
||||
Arrays.fill(bytes, (byte) 0x00);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testShortHeaderInDecryptor() throws InterruptedException {
|
||||
final byte[] keyBytes = new byte[32];
|
||||
@@ -137,45 +123,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];
|
||||
|
||||
@@ -35,20 +35,6 @@ public class FileContentEncryptorImplTest {
|
||||
|
||||
};
|
||||
|
||||
private static final SecureRandom RANDOM_MOCK_2 = new SecureRandom() {
|
||||
|
||||
@Override
|
||||
public int nextInt(int bound) {
|
||||
return 42;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nextBytes(byte[] bytes) {
|
||||
Arrays.fill(bytes, (byte) 0x00);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testEncryption() throws InterruptedException {
|
||||
final byte[] keyBytes = new byte[32];
|
||||
@@ -95,24 +81,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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -43,6 +43,11 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws UncheckedIOException {
|
||||
return content.get().limit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(File destination) throws UncheckedIOException {
|
||||
if (destination instanceof InMemoryFile) {
|
||||
@@ -103,7 +108,7 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
throw new UncheckedIOException(new FileAlreadyExistsException(k));
|
||||
} else {
|
||||
if (v == null) {
|
||||
assert!content.get().hasRemaining();
|
||||
assert !content.get().hasRemaining();
|
||||
this.creationTime = Instant.now();
|
||||
}
|
||||
this.lastModified = Instant.now();
|
||||
@@ -120,7 +125,7 @@ class InMemoryFile extends InMemoryNode implements File {
|
||||
// returning null removes the entry.
|
||||
return null;
|
||||
});
|
||||
assert!this.exists();
|
||||
assert !this.exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -51,11 +51,6 @@ class InMemoryReadableFile implements ReadableFile {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws UncheckedIOException {
|
||||
return contentGetter.get().limit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void position(long position) throws UncheckedIOException {
|
||||
assert position < Integer.MAX_VALUE : "Can not use that big in-memory files.";
|
||||
|
||||
@@ -104,9 +104,7 @@ public class InMemoryFileSystemTest {
|
||||
Assert.assertTrue(fooFile.exists());
|
||||
|
||||
// check if size = 11 bytes
|
||||
try (ReadableFile readable = fooFile.openReadable()) {
|
||||
Assert.assertEquals(11, readable.size());
|
||||
}
|
||||
Assert.assertEquals(11, fooFile.size());
|
||||
|
||||
// copy foo to bar
|
||||
File barFile = fs.file("bar.txt");
|
||||
|
||||
@@ -17,6 +17,11 @@ import java.util.stream.Stream;
|
||||
|
||||
class DefaultNioAccess implements NioAccess {
|
||||
|
||||
@Override
|
||||
public long size(Path path) throws IOException {
|
||||
return Files.size(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsynchronousFileChannel open(Path path, OpenOption... options) throws IOException {
|
||||
return AsynchronousFileChannel.open(path, options);
|
||||
@@ -59,7 +64,8 @@ class DefaultNioAccess implements NioAccess {
|
||||
} catch (AccessDeniedException e) {
|
||||
// workaround for https://github.com/cryptomator/cryptomator/issues/317
|
||||
try {
|
||||
if (path.toFile().delete()) return;
|
||||
if (path.toFile().delete())
|
||||
return;
|
||||
} catch (UnsupportedOperationException e2) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ interface NioAccess {
|
||||
|
||||
public static final Holder<NioAccess> DEFAULT = new Holder<>(new DefaultNioAccess());
|
||||
|
||||
long size(Path path) throws IOException;
|
||||
|
||||
AsynchronousFileChannel open(Path path, OpenOption... options) throws IOException;
|
||||
|
||||
boolean isRegularFile(Path path, LinkOption... options);
|
||||
|
||||
@@ -27,6 +27,15 @@ class NioFile extends NioNode implements File {
|
||||
sharedChannel = instanceFactory.sharedFileChannel(path, nioAccess);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws UncheckedIOException {
|
||||
try {
|
||||
return nioAccess.size(path);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadableFile openReadable() throws UncheckedIOException {
|
||||
if (lock.getWriteHoldCount() > 0) {
|
||||
|
||||
@@ -41,11 +41,6 @@ class ReadableNioFile implements ReadableFile {
|
||||
return open;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws UncheckedIOException {
|
||||
return channel.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void position(long position) throws UncheckedIOException {
|
||||
assertOpen();
|
||||
|
||||
@@ -16,6 +16,7 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
@@ -85,6 +86,27 @@ public class NioFileTest {
|
||||
|
||||
}
|
||||
|
||||
public class Size {
|
||||
|
||||
@Test
|
||||
public void testSizeReturnsSizeOfRegularFile() throws IOException {
|
||||
when(nioAccess.size(path)).thenReturn(42l);
|
||||
|
||||
assertThat(inTest.size(), is(42l));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSizeThrowsExceptionIfRegularFileThrowsException() throws IOException {
|
||||
Throwable t = new NoSuchFileException("foo");
|
||||
when(nioAccess.size(path)).thenThrow(t);
|
||||
|
||||
thrown.expect(UncheckedIOException.class);
|
||||
thrown.expectCause(org.hamcrest.Matchers.sameInstance(t));
|
||||
inTest.size();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class Open {
|
||||
|
||||
@Test
|
||||
|
||||
@@ -83,16 +83,6 @@ public class ReadableNioFileTest {
|
||||
inTest.position(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSizeReturnsSizeOfChannel() {
|
||||
long expectedSize = 85472;
|
||||
when(channel.size()).thenReturn(expectedSize);
|
||||
|
||||
long actualSize = inTest.size();
|
||||
|
||||
assertThat(actualSize, is(expectedSize));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadDelegatesToChannelReadFullyWithZeroPositionIfNotSet() {
|
||||
ByteBuffer buffer = mock(ByteBuffer.class);
|
||||
|
||||
@@ -7,8 +7,10 @@ package org.cryptomator.frontend.webdav;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.System.currentTimeMillis;
|
||||
import static java.util.Collections.synchronizedSet;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@@ -27,18 +29,15 @@ class Tarpit implements Serializable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(Tarpit.class);
|
||||
private static final long DELAY_MS = 10000;
|
||||
|
||||
private final Set<FrontendId> validFrontendIds = new HashSet<>();
|
||||
private final Set<FrontendId> validFrontendIds = synchronizedSet(new HashSet<>());
|
||||
|
||||
@Inject
|
||||
public Tarpit() {
|
||||
}
|
||||
public Tarpit() {}
|
||||
|
||||
public void register(FrontendId frontendId) {
|
||||
validFrontendIds.add(frontendId);
|
||||
}
|
||||
|
||||
public void unregister(FrontendId frontendId) {
|
||||
validFrontendIds.remove(frontendId);
|
||||
public void setValidFrontendIds(Collection<FrontendId> validFrontendIds) {
|
||||
this.validFrontendIds.retainAll(validFrontendIds);
|
||||
this.validFrontendIds.addAll(validFrontendIds);
|
||||
validFrontendIds.forEach(System.out::println);
|
||||
}
|
||||
|
||||
public void handle(HttpServletRequest req) {
|
||||
|
||||
@@ -24,15 +24,13 @@ class WebDavFrontend implements Frontend {
|
||||
private final WebDavMounterProvider webdavMounterProvider;
|
||||
private final ServletContextHandler handler;
|
||||
private final URI uri;
|
||||
private final Runnable afterClose;
|
||||
|
||||
private WebDavMount mount;
|
||||
|
||||
public WebDavFrontend(WebDavMounterProvider webdavMounterProvider, ServletContextHandler handler, URI uri, Runnable afterUnmount) throws FrontendCreationFailedException {
|
||||
public WebDavFrontend(WebDavMounterProvider webdavMounterProvider, ServletContextHandler handler, URI uri) throws FrontendCreationFailedException {
|
||||
this.webdavMounterProvider = webdavMounterProvider;
|
||||
this.handler = handler;
|
||||
this.uri = uri;
|
||||
this.afterClose = afterUnmount;
|
||||
try {
|
||||
handler.start();
|
||||
} catch (Exception e) {
|
||||
@@ -42,12 +40,8 @@ class WebDavFrontend implements Frontend {
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
try {
|
||||
unmount();
|
||||
handler.stop();
|
||||
} finally {
|
||||
afterClose.run();
|
||||
}
|
||||
unmount();
|
||||
handler.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -12,6 +12,7 @@ import static java.lang.String.format;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
@@ -117,9 +118,12 @@ public class WebDavServer implements FrontendFactory {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
final ServletContextHandler handler = addServlet(root, uri);
|
||||
tarpit.register(id);
|
||||
LOG.info("Servlet available under " + uri);
|
||||
return new WebDavFrontend(webdavMounterProvider, handler, uri, () -> tarpit.unregister(id));
|
||||
return new WebDavFrontend(webdavMounterProvider, handler, uri);
|
||||
}
|
||||
|
||||
public void setValidFrontendIds(Collection<FrontendId> validFrontendIds) {
|
||||
tarpit.setValidFrontendIds(validFrontendIds);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,14 +32,11 @@ import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
import org.cryptomator.filesystem.jackrabbit.FileLocator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
class DavFile extends DavNode<FileLocator> {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DavFile.class);
|
||||
protected static final String CONTENT_TYPE_VALUE = "application/octet-stream";
|
||||
protected static final String CONTENT_DISPOSITION_HEADER = "Content-Disposition";
|
||||
protected static final String CONTENT_DISPOSITION_VALUE = "attachment";
|
||||
@@ -64,8 +61,8 @@ class DavFile extends DavNode<FileLocator> {
|
||||
outputContext.setContentType(CONTENT_TYPE_VALUE);
|
||||
outputContext.setProperty(CONTENT_DISPOSITION_HEADER, CONTENT_DISPOSITION_VALUE);
|
||||
outputContext.setProperty(X_CONTENT_TYPE_OPTIONS_HEADER, X_CONTENT_TYPE_OPTIONS_VALUE);
|
||||
outputContext.setContentLength(node.size());
|
||||
try (ReadableFile src = node.openReadable(); WritableByteChannel dst = Channels.newChannel(outputContext.getOutputStream())) {
|
||||
outputContext.setContentLength(src.size());
|
||||
ByteStreams.copy(src, dst);
|
||||
}
|
||||
}
|
||||
@@ -157,12 +154,7 @@ class DavFile extends DavNode<FileLocator> {
|
||||
|
||||
private Optional<DavProperty<?>> sizeProperty() {
|
||||
if (node.exists()) {
|
||||
try (ReadableFile src = node.openReadable()) {
|
||||
return Optional.of(new DefaultDavProperty<Long>(DavPropertyName.GETCONTENTLENGTH, src.size()));
|
||||
} catch (RuntimeException e) {
|
||||
LOG.warn("Could not determine file size of " + getResourcePath(), e);
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(new DefaultDavProperty<Long>(DavPropertyName.GETCONTENTLENGTH, node.size()));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ class DavFileWithRange extends DavFile {
|
||||
if (!outputContext.hasStream()) {
|
||||
return;
|
||||
}
|
||||
final long contentLength = node.size();
|
||||
try (ReadableFile src = node.openReadable(); OutputStream out = outputContext.getOutputStream()) {
|
||||
final long contentLength = src.size();
|
||||
final Pair<Long, Long> range = getEffectiveRange(contentLength);
|
||||
if (range.getLeft() < 0 || range.getLeft() > range.getRight() || range.getRight() > contentLength) {
|
||||
outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), "bytes */" + contentLength);
|
||||
|
||||
@@ -39,10 +39,10 @@ class DavFileWithUnsatisfiableRange extends DavFile {
|
||||
if (!outputContext.hasStream()) {
|
||||
return;
|
||||
}
|
||||
final long contentLength = node.size();
|
||||
outputContext.setContentLength(contentLength);
|
||||
outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), "bytes */" + contentLength);
|
||||
try (ReadableFile src = node.openReadable(); OutputStream out = outputContext.getOutputStream()) {
|
||||
final long contentLength = src.size();
|
||||
outputContext.setContentLength(contentLength);
|
||||
outputContext.setProperty(HttpHeader.CONTENT_RANGE.asString(), "bytes */" + contentLength);
|
||||
ByteStreams.copy(src, Channels.newChannel(out));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<!-- dependency versions -->
|
||||
<cryptomator.cryptolib.version>1.0.0-SNAPSHOT</cryptomator.cryptolib.version>
|
||||
<cryptomator.jni.version>1.0.0-SNAPSHOT</cryptomator.jni.version>
|
||||
<log4j.version>2.1</log4j.version>
|
||||
<slf4j.version>1.7.7</slf4j.version>
|
||||
@@ -124,7 +125,12 @@
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Cryptomator libraries -->
|
||||
<!-- Cryptomator Libs -->
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptolib</artifactId>
|
||||
<version>${cryptomator.cryptolib.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>jni</artifactId>
|
||||
|
||||
@@ -59,6 +59,12 @@
|
||||
<artifactId>jni</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- CryptoLib -->
|
||||
<dependency>
|
||||
<groupId>org.cryptomator</groupId>
|
||||
<artifactId>cryptolib</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- EasyBind -->
|
||||
<dependency>
|
||||
<groupId>org.fxmisc.easybind</groupId>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
*******************************************************************************/
|
||||
package org.cryptomator.ui;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
@@ -18,11 +20,14 @@ import javax.inject.Singleton;
|
||||
import org.cryptomator.common.CommonsModule;
|
||||
import org.cryptomator.crypto.engine.impl.CryptoEngineModule;
|
||||
import org.cryptomator.frontend.FrontendFactory;
|
||||
import org.cryptomator.frontend.FrontendId;
|
||||
import org.cryptomator.frontend.webdav.WebDavModule;
|
||||
import org.cryptomator.frontend.webdav.WebDavServer;
|
||||
import org.cryptomator.jni.JniModule;
|
||||
import org.cryptomator.jni.MacFunctions;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.model.VaultObjectMapperProvider;
|
||||
import org.cryptomator.ui.model.Vaults;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.settings.SettingsProvider;
|
||||
import org.cryptomator.ui.util.DeferredCloser;
|
||||
@@ -34,6 +39,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import javafx.application.Application;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@Module(includes = {CryptoEngineModule.class, CommonsModule.class, WebDavModule.class})
|
||||
@@ -96,7 +102,9 @@ class CryptomatorModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
FrontendFactory provideFrontendFactory(DeferredCloser closer, WebDavServer webDavServer, Settings settings) {
|
||||
FrontendFactory provideFrontendFactory(DeferredCloser closer, WebDavServer webDavServer, Vaults vaults, Settings settings) {
|
||||
vaults.addListener((Observable o) -> setValidFrontendIds(webDavServer, vaults));
|
||||
setValidFrontendIds(webDavServer, vaults);
|
||||
webDavServer.setPort(settings.getPort());
|
||||
webDavServer.start();
|
||||
return closer.closeLater(webDavServer, WebDavServer::stop).get().orElseThrow(IllegalStateException::new);
|
||||
@@ -108,4 +116,9 @@ class CryptomatorModule {
|
||||
return JniModule.macFunctions();
|
||||
}
|
||||
|
||||
private void setValidFrontendIds(WebDavServer webDavServer, Vaults vaults) {
|
||||
webDavServer.setValidFrontendIds(vaults.stream() //
|
||||
.map(Vault::getId).map(FrontendId::from).collect(toList()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.cryptomator.ui.controls.DirectoryListCell;
|
||||
import org.cryptomator.ui.model.UpgradeStrategies;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.model.VaultFactory;
|
||||
import org.cryptomator.ui.model.Vaults;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.cryptomator.ui.util.DialogBuilderUtil;
|
||||
@@ -44,9 +45,6 @@ import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.BooleanExpression;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener.Change;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Side;
|
||||
@@ -81,7 +79,7 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
private final Lazy<SettingsController> settingsController;
|
||||
private final Lazy<UpgradeStrategies> upgradeStrategies;
|
||||
private final ObjectProperty<AbstractFXMLViewController> activeController = new SimpleObjectProperty<>();
|
||||
private final ObservableList<Vault> vaults;
|
||||
private final Vaults vaults;
|
||||
private final ObjectProperty<Vault> selectedVault = new SimpleObjectProperty<>();
|
||||
private final BooleanExpression isSelectedVaultUnlocked = BooleanBinding.booleanExpression(EasyBind.select(selectedVault).selectObject(Vault::unlockedProperty).orElse(false));
|
||||
private final BooleanExpression isSelectedVaultValid = BooleanBinding.booleanExpression(EasyBind.monadic(selectedVault).map(Vault::isValidVaultDirectory).orElse(false));
|
||||
@@ -92,7 +90,8 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
@Inject
|
||||
public MainController(@Named("mainWindow") Stage mainWindow, Localization localization, Settings settings, VaultFactory vaultFactoy, Lazy<WelcomeController> welcomeController,
|
||||
Lazy<InitializeController> initializeController, Lazy<NotFoundController> notFoundController, Lazy<UpgradeController> upgradeController, Lazy<UnlockController> unlockController,
|
||||
Provider<UnlockedController> unlockedControllerProvider, Lazy<ChangePasswordController> changePasswordController, Lazy<SettingsController> settingsController, Lazy<UpgradeStrategies> upgradeStrategies) {
|
||||
Provider<UnlockedController> unlockedControllerProvider, Lazy<ChangePasswordController> changePasswordController, Lazy<SettingsController> settingsController, Lazy<UpgradeStrategies> upgradeStrategies,
|
||||
Vaults vaults) {
|
||||
super(localization);
|
||||
this.mainWindow = mainWindow;
|
||||
this.vaultFactoy = vaultFactoy;
|
||||
@@ -105,10 +104,7 @@ public class MainController extends LocalizedFXMLViewController {
|
||||
this.changePasswordController = changePasswordController;
|
||||
this.settingsController = settingsController;
|
||||
this.upgradeStrategies = upgradeStrategies;
|
||||
this.vaults = FXCollections.observableList(settings.getDirectories());
|
||||
this.vaults.addListener((Change<? extends Vault> c) -> {
|
||||
settings.save();
|
||||
});
|
||||
this.vaults = vaults;
|
||||
|
||||
// derived bindings:
|
||||
this.isShowingSettings = activeController.isEqualTo(settingsController.get());
|
||||
|
||||
@@ -14,8 +14,8 @@ public class UpgradeStrategies {
|
||||
private final Collection<UpgradeStrategy> strategies;
|
||||
|
||||
@Inject
|
||||
public UpgradeStrategies(UpgradeVersion3DropBundleExtension upgrader1, UpgradeVersion3to4 upgrader2) {
|
||||
strategies = Collections.unmodifiableList(Arrays.asList(upgrader1, upgrader2));
|
||||
public UpgradeStrategies(UpgradeVersion3DropBundleExtension upgrader1, UpgradeVersion3to4 upgrader2, UpgradeVersion4to5 upgrader3) {
|
||||
strategies = Collections.unmodifiableList(Arrays.asList(upgrader1, upgrader2, upgrader3));
|
||||
}
|
||||
|
||||
public Optional<UpgradeStrategy> getUpgradeStrategy(Vault vault) {
|
||||
|
||||
@@ -6,11 +6,11 @@ import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.api.CryptorProvider;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.cryptolib.api.KeyFile;
|
||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.filesystem.crypto.Constants;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.slf4j.Logger;
|
||||
@@ -20,12 +20,16 @@ public abstract class UpgradeStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeStrategy.class);
|
||||
|
||||
protected final Provider<Cryptor> cryptorProvider;
|
||||
protected final CryptorProvider cryptorProvider;
|
||||
protected final Localization localization;
|
||||
protected final int vaultVersionBeforeUpgrade;
|
||||
protected final int vaultVersionAfterUpgrade;
|
||||
|
||||
UpgradeStrategy(Provider<Cryptor> cryptorProvider, Localization localization) {
|
||||
UpgradeStrategy(CryptorProvider cryptorProvider, Localization localization, int vaultVersionBeforeUpgrade, int vaultVersionAfterUpgrade) {
|
||||
this.cryptorProvider = cryptorProvider;
|
||||
this.localization = localization;
|
||||
this.vaultVersionBeforeUpgrade = vaultVersionBeforeUpgrade;
|
||||
this.vaultVersionAfterUpgrade = vaultVersionAfterUpgrade;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,27 +41,29 @@ public abstract class UpgradeStrategy {
|
||||
* Upgrades a vault. Might take a moment, should be run in a background thread.
|
||||
*/
|
||||
public void upgrade(Vault vault, CharSequence passphrase) throws UpgradeFailedException {
|
||||
final Cryptor cryptor = cryptorProvider.get();
|
||||
Cryptor cryptor = null;
|
||||
try {
|
||||
final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME);
|
||||
final byte[] masterkeyFileContents = Files.readAllBytes(masterkeyFile);
|
||||
cryptor.readKeysFromMasterkeyFile(masterkeyFileContents, passphrase);
|
||||
cryptor = cryptorProvider.createFromKeyFile(KeyFile.parse(masterkeyFileContents), passphrase, vaultVersionBeforeUpgrade);
|
||||
// create backup, as soon as we know the password was correct:
|
||||
final Path masterkeyBackupFile = vault.path().getValue().resolve(Constants.MASTERKEY_BACKUP_FILENAME);
|
||||
Files.copy(masterkeyFile, masterkeyBackupFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
// do stuff:
|
||||
upgrade(vault, cryptor);
|
||||
// write updated masterkey file:
|
||||
final byte[] upgradedMasterkeyFileContents = cryptor.writeKeysToMasterkeyFile(passphrase);
|
||||
final Path masterkeyFileAfterUpgrading = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME); // path may have changed
|
||||
Files.write(masterkeyFileAfterUpgrading, upgradedMasterkeyFileContents, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
final byte[] upgradedMasterkeyFileContents = cryptor.writeKeysToMasterkeyFile(passphrase, vaultVersionAfterUpgrade).serialize();
|
||||
final Path masterkeyFileAfterUpgrade = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME); // path may have changed
|
||||
Files.write(masterkeyFileAfterUpgrade, upgradedMasterkeyFileContents, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
} catch (InvalidPassphraseException e) {
|
||||
throw new UpgradeFailedException(localization.getString("unlock.errorMessage.wrongPassword"));
|
||||
} catch (IOException | UnsupportedVaultFormatException e) {
|
||||
LOG.warn("Upgrade failed.", e);
|
||||
throw new UpgradeFailedException("Upgrade failed. Details in log message.");
|
||||
} finally {
|
||||
cryptor.destroy();
|
||||
if (cryptor != null) {
|
||||
cryptor.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +74,21 @@ public abstract class UpgradeStrategy {
|
||||
*
|
||||
* @return <code>true</code> if and only if the vault can be migrated to a newer version without the risk of data losses.
|
||||
*/
|
||||
public abstract boolean isApplicable(Vault vault);
|
||||
public boolean isApplicable(Vault vault) {
|
||||
final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME);
|
||||
try {
|
||||
if (Files.isRegularFile(masterkeyFile)) {
|
||||
byte[] masterkeyFileContents = Files.readAllBytes(masterkeyFile);
|
||||
return KeyFile.parse(masterkeyFileContents).getVersion() == vaultVersionBeforeUpgrade;
|
||||
} else {
|
||||
LOG.warn("Not a file: {}", masterkeyFile);
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not determine, whether upgrade is applicable.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown when data migration failed.
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.filesystem.crypto.Constants;
|
||||
import org.cryptomator.cryptolib.Cryptors;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
import org.slf4j.Logger;
|
||||
@@ -28,8 +25,8 @@ class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
|
||||
private final Settings settings;
|
||||
|
||||
@Inject
|
||||
public UpgradeVersion3DropBundleExtension(Provider<Cryptor> cryptorProvider, Localization localization, Settings settings) {
|
||||
super(cryptorProvider, localization);
|
||||
public UpgradeVersion3DropBundleExtension(SecureRandom secureRandom, Localization localization, Settings settings) {
|
||||
super(Cryptors.version1(secureRandom), localization, 3, 3);
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@@ -42,25 +39,6 @@ class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
|
||||
return String.format(fmt, oldVaultName, newVaultName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upgrade(Vault vault, CharSequence passphrase) throws UpgradeFailedException {
|
||||
final Cryptor cryptor = cryptorProvider.get();
|
||||
try {
|
||||
final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME);
|
||||
final byte[] masterkeyFileContents = Files.readAllBytes(masterkeyFile);
|
||||
cryptor.readKeysFromMasterkeyFile(masterkeyFileContents, passphrase);
|
||||
upgrade(vault, cryptor);
|
||||
// don't write new masterkey. this is a special case, as we were stupid and didn't increase the vault version with this upgrade...
|
||||
} catch (InvalidPassphraseException e) {
|
||||
throw new UpgradeFailedException(localization.getString("unlock.errorMessage.wrongPassword"));
|
||||
} catch (IOException | UnsupportedVaultFormatException e) {
|
||||
LOG.warn("Upgrade failed.", e);
|
||||
throw new UpgradeFailedException("Upgrade failed. Details in log message.");
|
||||
} finally {
|
||||
cryptor.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException {
|
||||
Path path = vault.path().getValue();
|
||||
@@ -73,6 +51,7 @@ class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
|
||||
throw new UpgradeFailedException(msg);
|
||||
} else {
|
||||
try {
|
||||
LOG.info("Renaming {} to {}", path, newPath.getFileName());
|
||||
Files.move(path, path.resolveSibling(newVaultName));
|
||||
Platform.runLater(() -> {
|
||||
vault.setPath(newPath);
|
||||
@@ -89,19 +68,7 @@ class UpgradeVersion3DropBundleExtension extends UpgradeStrategy {
|
||||
public boolean isApplicable(Vault vault) {
|
||||
Path vaultPath = vault.path().getValue();
|
||||
if (vaultPath.toString().endsWith(Vault.VAULT_FILE_EXTENSION)) {
|
||||
final Path masterkeyFile = vaultPath.resolve(Constants.MASTERKEY_FILENAME);
|
||||
try {
|
||||
if (Files.isRegularFile(masterkeyFile)) {
|
||||
final String keyContents = new String(Files.readAllBytes(masterkeyFile), StandardCharsets.UTF_8);
|
||||
return keyContents.contains("\"version\":3") || keyContents.contains("\"version\": 3");
|
||||
} else {
|
||||
LOG.warn("Not a file: {}", masterkeyFile);
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not determine, whether upgrade is applicable.", e);
|
||||
return false;
|
||||
}
|
||||
return super.isApplicable(vault);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -9,19 +9,19 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.codec.binary.Base32;
|
||||
import org.apache.commons.codec.binary.BaseNCodec;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.filesystem.crypto.Constants;
|
||||
import org.cryptomator.cryptolib.Cryptors;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.common.MessageDigestSupplier;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -40,17 +40,12 @@ class UpgradeVersion3to4 extends UpgradeStrategy {
|
||||
private static final String OLD_FOLDER_SUFFIX = "_";
|
||||
private static final String NEW_FOLDER_PREFIX = "0";
|
||||
|
||||
private final MessageDigest sha1;
|
||||
private final MessageDigest sha1 = MessageDigestSupplier.SHA1.get();
|
||||
private final BaseNCodec base32 = new Base32();
|
||||
|
||||
@Inject
|
||||
public UpgradeVersion3to4(Provider<Cryptor> cryptorProvider, Localization localization) {
|
||||
super(cryptorProvider, localization);
|
||||
try {
|
||||
sha1 = MessageDigest.getInstance("SHA-1");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError("SHA-1 exists in every JVM");
|
||||
}
|
||||
public UpgradeVersion3to4(SecureRandom secureRandom, Localization localization) {
|
||||
super(Cryptors.version1(secureRandom), localization, 3, 4);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,21 +139,4 @@ class UpgradeVersion3to4 extends UpgradeStrategy {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isApplicable(Vault vault) {
|
||||
final Path masterkeyFile = vault.path().getValue().resolve(Constants.MASTERKEY_FILENAME);
|
||||
try {
|
||||
if (Files.isRegularFile(masterkeyFile)) {
|
||||
final String keyContents = new String(Files.readAllBytes(masterkeyFile), UTF_8);
|
||||
return keyContents.contains("\"version\":3") || keyContents.contains("\"version\": 3");
|
||||
} else {
|
||||
LOG.warn("Not a file: {}", masterkeyFile);
|
||||
return false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Could not determine, whether upgrade is applicable.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.cryptolib.Cryptors;
|
||||
import org.cryptomator.cryptolib.api.Cryptor;
|
||||
import org.cryptomator.cryptolib.api.FileHeader;
|
||||
import org.cryptomator.ui.settings.Localization;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Contains the collective knowledge of all creatures who were alive during the development of vault format 3.
|
||||
* This class uses no external classes from the crypto or shortening layer by purpose, so we don't need legacy code inside these.
|
||||
*/
|
||||
@Singleton
|
||||
class UpgradeVersion4to5 extends UpgradeStrategy {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UpgradeVersion4to5.class);
|
||||
private static final Pattern BASE32_PATTERN = Pattern.compile("^([A-Z2-7]{8})*[A-Z2-7=]{8}");
|
||||
|
||||
@Inject
|
||||
public UpgradeVersion4to5(SecureRandom secureRandom, Localization localization) {
|
||||
super(Cryptors.version1(secureRandom), localization, 4, 5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNotification(Vault vault) {
|
||||
return localization.getString("upgrade.version3to4.msg");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void upgrade(Vault vault, Cryptor cryptor) throws UpgradeFailedException {
|
||||
Path dataDir = vault.path().get().resolve("d");
|
||||
if (!Files.isDirectory(dataDir)) {
|
||||
return; // empty vault. no migration needed.
|
||||
}
|
||||
try {
|
||||
Files.walkFileTree(dataDir, new SimpleFileVisitor<Path>() {
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
if (BASE32_PATTERN.matcher(file.getFileName().toString()).find() && attrs.size() > cryptor.fileHeaderCryptor().headerSize()) {
|
||||
migrate(file, attrs, cryptor);
|
||||
}
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.error("Migration failed.", e);
|
||||
throw new UpgradeFailedException(localization.getString("upgrade.version3to4.err.io"));
|
||||
}
|
||||
LOG.info("Migration finished.");
|
||||
}
|
||||
|
||||
private void migrate(Path file, BasicFileAttributes attrs, Cryptor cryptor) throws IOException {
|
||||
try (FileChannel ch = FileChannel.open(file, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
|
||||
// read header:
|
||||
ByteBuffer headerBuf = ByteBuffer.allocate(cryptor.fileHeaderCryptor().headerSize());
|
||||
ch.read(headerBuf);
|
||||
headerBuf.flip();
|
||||
FileHeader header = cryptor.fileHeaderCryptor().decryptHeader(headerBuf);
|
||||
long cleartextSize = header.getFilesize();
|
||||
if (cleartextSize < 0) {
|
||||
LOG.info("Skipping already migrated file {}.", file);
|
||||
return;
|
||||
} else if (cleartextSize > attrs.size()) {
|
||||
LOG.warn("Skipping file {} with invalid file size {}/{}", file, cleartextSize, attrs.size());
|
||||
return;
|
||||
}
|
||||
int headerSize = cryptor.fileHeaderCryptor().headerSize();
|
||||
int ciphertextChunkSize = cryptor.fileContentCryptor().ciphertextChunkSize();
|
||||
int cleartextChunkSize = cryptor.fileContentCryptor().cleartextChunkSize();
|
||||
long newCiphertextSize = Cryptors.ciphertextSize(cleartextSize, cryptor);
|
||||
long newEOF = headerSize + newCiphertextSize;
|
||||
long newFullChunks = newCiphertextSize / ciphertextChunkSize; // int-truncation
|
||||
long newAdditionalCiphertextBytes = newCiphertextSize % ciphertextChunkSize;
|
||||
if (newAdditionalCiphertextBytes == 0) {
|
||||
// (new) last block is already correct. just truncate:
|
||||
LOG.info("Migrating {} of cleartext size {}: Truncating to new ciphertext size: {}", file, cleartextSize, newEOF);
|
||||
ch.truncate(newEOF);
|
||||
} else {
|
||||
// last block may contain padding and needs to be re-encrypted:
|
||||
long lastChunkIdx = newFullChunks;
|
||||
LOG.info("Migrating {} of cleartext size {}: Re-encrypting chunk {}. New ciphertext size: {}", file, cleartextSize, lastChunkIdx, newEOF);
|
||||
long beginOfLastChunk = headerSize + lastChunkIdx * ciphertextChunkSize;
|
||||
assert beginOfLastChunk < newEOF;
|
||||
int lastCleartextChunkLength = (int) (cleartextSize % cleartextChunkSize);
|
||||
assert lastCleartextChunkLength < cleartextChunkSize;
|
||||
assert lastCleartextChunkLength > 0;
|
||||
ch.position(beginOfLastChunk);
|
||||
ByteBuffer lastCiphertextChunk = ByteBuffer.allocate(ciphertextChunkSize);
|
||||
int read = ch.read(lastCiphertextChunk);
|
||||
if (read != -1) {
|
||||
lastCiphertextChunk.flip();
|
||||
ByteBuffer lastCleartextChunk = cryptor.fileContentCryptor().decryptChunk(lastCiphertextChunk, lastChunkIdx, header, true);
|
||||
lastCleartextChunk.position(0).limit(lastCleartextChunkLength);
|
||||
assert lastCleartextChunk.remaining() == lastCleartextChunkLength;
|
||||
ByteBuffer newLastChunkCiphertext = cryptor.fileContentCryptor().encryptChunk(lastCleartextChunk, lastChunkIdx, header);
|
||||
ch.truncate(beginOfLastChunk);
|
||||
ch.write(newLastChunkCiphertext);
|
||||
} else {
|
||||
LOG.error("Reached EOF at position {}/{}", beginOfLastChunk, newEOF);
|
||||
return; // must exit method before changing header!
|
||||
}
|
||||
}
|
||||
header.setFilesize(-1l);
|
||||
ByteBuffer newHeaderBuf = cryptor.fileHeaderCryptor().encryptHeader(header);
|
||||
ch.position(0);
|
||||
ch.write(newHeaderBuf);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
177
main/ui/src/main/java/org/cryptomator/ui/model/Vaults.java
Normal file
177
main/ui/src/main/java/org/cryptomator/ui/model/Vaults.java
Normal file
@@ -0,0 +1,177 @@
|
||||
package org.cryptomator.ui.model;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.ui.settings.Settings;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ListChangeListener.Change;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
@Singleton
|
||||
public class Vaults implements ObservableList<Vault> {
|
||||
|
||||
private final ObservableList<Vault> delegate;
|
||||
|
||||
@Inject
|
||||
public Vaults(Settings settings) {
|
||||
this.delegate = FXCollections.observableList(settings.getDirectories());
|
||||
addListener((Change<? extends Vault> change) -> settings.save());
|
||||
}
|
||||
|
||||
public void addListener(ListChangeListener<? super Vault> listener) {
|
||||
delegate.addListener(listener);
|
||||
}
|
||||
|
||||
public void removeListener(ListChangeListener<? super Vault> listener) {
|
||||
delegate.removeListener(listener);
|
||||
}
|
||||
|
||||
public void addListener(InvalidationListener listener) {
|
||||
delegate.addListener(listener);
|
||||
}
|
||||
|
||||
public boolean addAll(Vault... elements) {
|
||||
return delegate.addAll(elements);
|
||||
}
|
||||
|
||||
public boolean setAll(Vault... elements) {
|
||||
return delegate.setAll(elements);
|
||||
}
|
||||
|
||||
public boolean setAll(Collection<? extends Vault> col) {
|
||||
return delegate.setAll(col);
|
||||
}
|
||||
|
||||
public boolean removeAll(Vault... elements) {
|
||||
return delegate.removeAll(elements);
|
||||
}
|
||||
|
||||
public void removeListener(InvalidationListener listener) {
|
||||
delegate.removeListener(listener);
|
||||
}
|
||||
|
||||
public boolean retainAll(Vault... elements) {
|
||||
return delegate.retainAll(elements);
|
||||
}
|
||||
|
||||
public void remove(int from, int to) {
|
||||
delegate.remove(from, to);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return delegate.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return delegate.isEmpty();
|
||||
}
|
||||
|
||||
public boolean contains(Object o) {
|
||||
return delegate.contains(o);
|
||||
}
|
||||
|
||||
public Iterator<Vault> iterator() {
|
||||
return delegate.iterator();
|
||||
}
|
||||
|
||||
public Object[] toArray() {
|
||||
return delegate.toArray();
|
||||
}
|
||||
|
||||
public <T> T[] toArray(T[] a) {
|
||||
return delegate.toArray(a);
|
||||
}
|
||||
|
||||
public boolean add(Vault e) {
|
||||
return delegate.add(e);
|
||||
}
|
||||
|
||||
public boolean remove(Object o) {
|
||||
return delegate.remove(o);
|
||||
}
|
||||
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
return delegate.containsAll(c);
|
||||
}
|
||||
|
||||
public boolean addAll(Collection<? extends Vault> c) {
|
||||
return delegate.addAll(c);
|
||||
}
|
||||
|
||||
public boolean addAll(int index, Collection<? extends Vault> c) {
|
||||
return delegate.addAll(index, c);
|
||||
}
|
||||
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
return delegate.removeAll(c);
|
||||
}
|
||||
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
return delegate.retainAll(c);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
delegate.clear();
|
||||
}
|
||||
|
||||
public Vault get(int index) {
|
||||
return delegate.get(index);
|
||||
}
|
||||
|
||||
public Vault set(int index, Vault element) {
|
||||
return delegate.set(index, element);
|
||||
}
|
||||
|
||||
public void add(int index, Vault element) {
|
||||
delegate.add(index, element);
|
||||
}
|
||||
|
||||
public Vault remove(int index) {
|
||||
return delegate.remove(index);
|
||||
}
|
||||
|
||||
public int indexOf(Object o) {
|
||||
return delegate.indexOf(o);
|
||||
}
|
||||
|
||||
public int lastIndexOf(Object o) {
|
||||
return delegate.lastIndexOf(o);
|
||||
}
|
||||
|
||||
public ListIterator<Vault> listIterator() {
|
||||
return delegate.listIterator();
|
||||
}
|
||||
|
||||
public ListIterator<Vault> listIterator(int index) {
|
||||
return delegate.listIterator(index);
|
||||
}
|
||||
|
||||
public List<Vault> subList(int fromIndex, int toIndex) {
|
||||
return delegate.subList(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
return internalEquals((Vaults)obj);
|
||||
}
|
||||
|
||||
private boolean internalEquals(Vaults other) {
|
||||
return delegate.equals(other.delegate);
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return delegate.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user