mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-25 06:01:31 +00:00
supporting change password again - now via CryptoFileSystemFactory
This commit is contained in:
@@ -21,8 +21,11 @@ public interface Cryptor extends Destroyable {
|
||||
|
||||
void randomizeMasterkey();
|
||||
|
||||
boolean readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence passphrase);
|
||||
void readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence passphrase) throws InvalidPassphraseException;
|
||||
|
||||
byte[] writeKeysToMasterkeyFile(CharSequence passphrase);
|
||||
|
||||
@Override
|
||||
void destroy();
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.cryptomator.common.LazyInitializer;
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.FileContentCryptor;
|
||||
import org.cryptomator.crypto.engine.FilenameCryptor;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -86,7 +87,7 @@ public class CryptorImpl implements Cryptor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence passphrase) {
|
||||
public void readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence passphrase) {
|
||||
final KeyFile keyFile;
|
||||
try {
|
||||
final ObjectMapper om = new ObjectMapper();
|
||||
@@ -107,9 +108,8 @@ public class CryptorImpl implements Cryptor {
|
||||
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);
|
||||
return true;
|
||||
} catch (InvalidKeyException e) {
|
||||
return false;
|
||||
throw new InvalidPassphraseException();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("Hard-coded algorithm doesn't exist.", e);
|
||||
} finally {
|
||||
@@ -152,17 +152,20 @@ public class CryptorImpl implements Cryptor {
|
||||
/* ======================= destruction ======================= */
|
||||
|
||||
@Override
|
||||
public void destroy() throws DestroyFailedException {
|
||||
public void destroy() {
|
||||
destroyQuietly(encryptionKey);
|
||||
destroyQuietly(macKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDestroyed() {
|
||||
return encryptionKey.isDestroyed() && macKey.isDestroyed();
|
||||
return (encryptionKey == null || encryptionKey.isDestroyed()) && (macKey == null || macKey.isDestroyed());
|
||||
}
|
||||
|
||||
private void destroyQuietly(Destroyable d) {
|
||||
if (d == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
d.destroy();
|
||||
} catch (DestroyFailedException e) {
|
||||
|
||||
@@ -16,60 +16,26 @@ import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
|
||||
public class CryptoFileSystem extends CryptoFolder implements FileSystem {
|
||||
|
||||
private static final String DATA_ROOT_DIR = "d";
|
||||
private static final String ROOT_DIR_FILE = "root";
|
||||
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator";
|
||||
private static final String MASTERKEY_BACKUP_FILENAME = "masterkey.cryptomator.bkup";
|
||||
|
||||
private final Folder physicalRoot;
|
||||
private final CryptoFileSystemDelegate delegate;
|
||||
|
||||
public CryptoFileSystem(Folder physicalRoot, Cryptor cryptor, CryptoFileSystemDelegate delegate, CharSequence passphrase) throws InvalidPassphraseException {
|
||||
super(null, "", cryptor);
|
||||
if (cryptor.isDestroyed()) {
|
||||
throw new IllegalArgumentException("Cryptor's keys must not be destroyed.");
|
||||
}
|
||||
this.physicalRoot = physicalRoot;
|
||||
this.delegate = delegate;
|
||||
final File masterkeyFile = physicalRoot.file(MASTERKEY_FILENAME);
|
||||
if (masterkeyFile.exists()) {
|
||||
final boolean unlocked = decryptMasterKeyFile(cryptor, masterkeyFile, passphrase);
|
||||
if (!unlocked) {
|
||||
throw new InvalidPassphraseException();
|
||||
}
|
||||
} else {
|
||||
cryptor.randomizeMasterkey();
|
||||
encryptMasterKeyFile(cryptor, masterkeyFile, passphrase);
|
||||
}
|
||||
assert masterkeyFile.exists() : "A CryptoFileSystem can not exist without a masterkey file.";
|
||||
final File backupFile = physicalRoot.file(MASTERKEY_BACKUP_FILENAME);
|
||||
masterkeyFile.copyTo(backupFile);
|
||||
create();
|
||||
}
|
||||
|
||||
private static boolean decryptMasterKeyFile(Cryptor cryptor, File masterkeyFile, CharSequence passphrase) {
|
||||
try (ReadableFile file = masterkeyFile.openReadable()) {
|
||||
// TODO we need to read the whole file but can not be sure about the
|
||||
// buffer size:
|
||||
final ByteBuffer bigEnoughBuffer = ByteBuffer.allocate(500);
|
||||
file.read(bigEnoughBuffer);
|
||||
bigEnoughBuffer.flip();
|
||||
assert bigEnoughBuffer.remaining() < bigEnoughBuffer.capacity() : "The buffer wasn't big enough.";
|
||||
final byte[] fileContents = new byte[bigEnoughBuffer.remaining()];
|
||||
bigEnoughBuffer.get(fileContents);
|
||||
return cryptor.readKeysFromMasterkeyFile(fileContents, passphrase);
|
||||
}
|
||||
}
|
||||
|
||||
private static void encryptMasterKeyFile(Cryptor cryptor, File masterkeyFile, CharSequence passphrase) {
|
||||
try (WritableFile file = masterkeyFile.openWritable()) {
|
||||
final byte[] fileContents = cryptor.writeKeysToMasterkeyFile(passphrase);
|
||||
file.write(ByteBuffer.wrap(fileContents));
|
||||
}
|
||||
}
|
||||
|
||||
CryptoFileSystemDelegate delegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package org.cryptomator.filesystem.crypto;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
@@ -14,20 +15,38 @@ import org.cryptomator.filesystem.shortening.ShorteningFileSystemFactory;
|
||||
@Singleton
|
||||
public class CryptoFileSystemFactory {
|
||||
|
||||
private final Provider<Cryptor> cryptorProvider;
|
||||
private final Masterkeys masterkeys;
|
||||
private final ShorteningFileSystemFactory shorteningFileSystemFactory;
|
||||
private final BlockAlignedFileSystemFactory blockAlignedFileSystemFactory;
|
||||
|
||||
@Inject
|
||||
public CryptoFileSystemFactory(Provider<Cryptor> cryptorProvider, ShorteningFileSystemFactory shorteningFileSystemFactory, BlockAlignedFileSystemFactory blockAlignedFileSystemFactory) {
|
||||
this.cryptorProvider = cryptorProvider;
|
||||
public CryptoFileSystemFactory(Masterkeys masterkeys, ShorteningFileSystemFactory shorteningFileSystemFactory, BlockAlignedFileSystemFactory blockAlignedFileSystemFactory) {
|
||||
this.masterkeys = masterkeys;
|
||||
this.shorteningFileSystemFactory = shorteningFileSystemFactory;
|
||||
this.blockAlignedFileSystemFactory = blockAlignedFileSystemFactory;
|
||||
}
|
||||
|
||||
public FileSystem get(Folder root, CharSequence passphrase, CryptoFileSystemDelegate delegate) throws InvalidPassphraseException {
|
||||
final FileSystem nameShorteningFs = shorteningFileSystemFactory.get(root);
|
||||
final FileSystem cryptoFs = new CryptoFileSystem(nameShorteningFs, cryptorProvider.get(), delegate, passphrase);
|
||||
public void initializeNew(Folder vaultLocation, CharSequence passphrase) {
|
||||
masterkeys.initialize(vaultLocation, passphrase);
|
||||
}
|
||||
|
||||
public FileSystem unlockExisting(Folder vaultLocation, CharSequence passphrase, CryptoFileSystemDelegate delegate) throws InvalidPassphraseException {
|
||||
final Cryptor cryptor = masterkeys.decrypt(vaultLocation, passphrase);
|
||||
masterkeys.backup(vaultLocation);
|
||||
final FileSystem nameShorteningFs = shorteningFileSystemFactory.get(vaultLocation);
|
||||
final FileSystem cryptoFs = new CryptoFileSystem(nameShorteningFs, cryptor, delegate, passphrase);
|
||||
return blockAlignedFileSystemFactory.get(cryptoFs);
|
||||
}
|
||||
|
||||
public void changePassphrase(Folder vaultLocation, CharSequence oldPassphrase, CharSequence newPassphrase) throws InvalidPassphraseException {
|
||||
masterkeys.backup(vaultLocation);
|
||||
try {
|
||||
masterkeys.changePassphrase(vaultLocation, oldPassphrase, newPassphrase);
|
||||
// At this point the backup is still using the old password.
|
||||
// It will be changed as soon as the user unlocks the vault the next time.
|
||||
// This way he can still restore the old password, if he doesn't remember the new one.
|
||||
} catch (UncheckedIOException e) {
|
||||
masterkeys.restoreBackup(vaultLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.cryptomator.filesystem.crypto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.WritableFile;
|
||||
|
||||
@Singleton
|
||||
class Masterkeys {
|
||||
|
||||
private static final String MASTERKEY_FILENAME = "masterkey.cryptomator";
|
||||
private static final String MASTERKEY_BACKUP_FILENAME = "masterkey.cryptomator.bkup";
|
||||
|
||||
private final Provider<Cryptor> cryptorProvider;
|
||||
|
||||
@Inject
|
||||
public Masterkeys(Provider<Cryptor> cryptorProvider) {
|
||||
this.cryptorProvider = cryptorProvider;
|
||||
}
|
||||
|
||||
public void initialize(Folder vaultLocation, CharSequence passphrase) {
|
||||
File masterkeyFile = vaultLocation.file(MASTERKEY_FILENAME);
|
||||
Cryptor cryptor = cryptorProvider.get();
|
||||
try {
|
||||
cryptor.randomizeMasterkey();
|
||||
writeMasterKey(masterkeyFile, cryptor, passphrase);
|
||||
} finally {
|
||||
cryptor.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
public Cryptor decrypt(Folder vaultLocation, CharSequence passphrase) throws InvalidPassphraseException {
|
||||
File masterkeyFile = vaultLocation.file(MASTERKEY_FILENAME);
|
||||
Cryptor cryptor = cryptorProvider.get();
|
||||
try {
|
||||
readMasterKey(masterkeyFile, cryptor, passphrase);
|
||||
} catch (UncheckedIOException e) {
|
||||
cryptor.destroy();
|
||||
}
|
||||
return cryptor;
|
||||
}
|
||||
|
||||
public void changePassphrase(Folder vaultLocation, CharSequence oldPassphrase, CharSequence newPassphrase) throws InvalidPassphraseException {
|
||||
File masterkeyFile = vaultLocation.file(MASTERKEY_FILENAME);
|
||||
Cryptor cryptor = cryptorProvider.get();
|
||||
try {
|
||||
readMasterKey(masterkeyFile, cryptor, oldPassphrase);
|
||||
writeMasterKey(masterkeyFile, cryptor, newPassphrase);
|
||||
} finally {
|
||||
cryptor.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
public void backup(Folder vaultLocation) {
|
||||
File masterkeyFile = vaultLocation.file(MASTERKEY_FILENAME);
|
||||
File backupFile = vaultLocation.file(MASTERKEY_BACKUP_FILENAME);
|
||||
masterkeyFile.copyTo(backupFile);
|
||||
}
|
||||
|
||||
public void restoreBackup(Folder vaultLocation) {
|
||||
File backupFile = vaultLocation.file(MASTERKEY_BACKUP_FILENAME);
|
||||
File masterkeyFile = vaultLocation.file(MASTERKEY_FILENAME);
|
||||
backupFile.copyTo(masterkeyFile);
|
||||
}
|
||||
|
||||
/* I/O */
|
||||
|
||||
private static void readMasterKey(File file, Cryptor cryptor, CharSequence passphrase) throws UncheckedIOException, InvalidPassphraseException {
|
||||
try (InputStream in = Channels.newInputStream(file.openReadable())) {
|
||||
final byte[] fileContents = IOUtils.toByteArray(in);
|
||||
cryptor.readKeysFromMasterkeyFile(fileContents, passphrase);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeMasterKey(File file, Cryptor cryptor, CharSequence passphrase) throws UncheckedIOException {
|
||||
try (WritableFile writable = file.openWritable()) {
|
||||
final byte[] fileContents = cryptor.writeKeysToMasterkeyFile(passphrase);
|
||||
writable.write(ByteBuffer.wrap(fileContents));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,9 +29,8 @@ public class NoCryptor implements Cryptor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence passphrase) {
|
||||
public void readKeysFromMasterkeyFile(byte[] masterkeyFileContents, CharSequence passphrase) {
|
||||
// thanks, but I don't need a key, if I'm not encryption anything...
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,4 +39,9 @@ public class NoCryptor implements Cryptor {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,19 +11,28 @@ package org.cryptomator.crypto.engine.impl;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class CryptorImplTest {
|
||||
|
||||
@Test
|
||||
public void testMasterkeyDecryption() throws IOException {
|
||||
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==\"}";
|
||||
final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl();
|
||||
Assert.assertFalse(cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "qwe"));
|
||||
Assert.assertTrue(cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd"));
|
||||
cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "asd");
|
||||
}
|
||||
|
||||
@Test(expected = InvalidPassphraseException.class)
|
||||
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==\"}";
|
||||
final Cryptor cryptor = TestCryptorImplFactory.insecureCryptorImpl();
|
||||
cryptor.readKeysFromMasterkeyFile(testMasterKey.getBytes(), "qwe");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.cryptomator.filesystem.crypto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -33,10 +34,35 @@ public class CryptoFileSystemComponentIntegrationTest {
|
||||
public void setupFileSystems() {
|
||||
cryptoDelegate = Mockito.mock(CryptoFileSystemDelegate.class);
|
||||
ciphertextFs = new InMemoryFileSystem();
|
||||
cleartextFs = cryptoFsComp.cryptoFileSystemFactory().get(ciphertextFs, "TopSecret", cryptoDelegate);
|
||||
cryptoFsComp.cryptoFileSystemFactory().initializeNew(ciphertextFs, "TopSecret");
|
||||
cleartextFs = cryptoFsComp.cryptoFileSystemFactory().unlockExisting(ciphertextFs, "TopSecret", cryptoDelegate);
|
||||
cleartextFs.create();
|
||||
}
|
||||
|
||||
@Test(timeout = 1000)
|
||||
public void testVaultStructureInitializationAndBackupBehaviour() throws UncheckedIOException, IOException {
|
||||
final FileSystem physicalFs = new InMemoryFileSystem();
|
||||
final File masterkeyFile = physicalFs.file("masterkey.cryptomator");
|
||||
final File masterkeyBkupFile = physicalFs.file("masterkey.cryptomator.bkup");
|
||||
final Folder physicalDataRoot = physicalFs.folder("d");
|
||||
Assert.assertFalse(masterkeyFile.exists());
|
||||
Assert.assertFalse(masterkeyBkupFile.exists());
|
||||
Assert.assertFalse(physicalDataRoot.exists());
|
||||
|
||||
cryptoFsComp.cryptoFileSystemFactory().initializeNew(physicalFs, "asd");
|
||||
Assert.assertTrue(masterkeyFile.exists());
|
||||
Assert.assertFalse(masterkeyBkupFile.exists());
|
||||
Assert.assertFalse(physicalDataRoot.exists());
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
final FileSystem cryptoFs = cryptoFsComp.cryptoFileSystemFactory().unlockExisting(physicalFs, "asd", cryptoDelegate);
|
||||
Assert.assertTrue(masterkeyBkupFile.exists());
|
||||
Assert.assertTrue(physicalDataRoot.exists());
|
||||
Assert.assertEquals(3, physicalFs.children().count()); // d + masterkey.cryptomator + masterkey.cryptomator.bkup
|
||||
Assert.assertEquals(1, physicalDataRoot.files().count()); // ROOT file
|
||||
Assert.assertEquals(1, physicalDataRoot.folders().count()); // ROOT directory
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryptionOfLongFolderNames() {
|
||||
final String shortName = "normal folder name";
|
||||
|
||||
@@ -13,13 +13,11 @@ import static org.cryptomator.filesystem.FileSystemVisitor.fileSystemVisitor;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.NoCryptor;
|
||||
import org.cryptomator.filesystem.File;
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.Folder;
|
||||
import org.cryptomator.filesystem.ReadableFile;
|
||||
@@ -31,58 +29,6 @@ import org.mockito.Mockito;
|
||||
|
||||
public class CryptoFileSystemTest {
|
||||
|
||||
@Test(timeout = 1000)
|
||||
public void testVaultStructureInitialization() throws UncheckedIOException, IOException {
|
||||
// mock cryptor:
|
||||
final Cryptor cryptor = new NoCryptor();
|
||||
|
||||
// some mock fs:
|
||||
final FileSystem physicalFs = new InMemoryFileSystem();
|
||||
final File masterkeyFile = physicalFs.file("masterkey.cryptomator");
|
||||
final File masterkeyBkupFile = physicalFs.file("masterkey.cryptomator.bkup");
|
||||
final Folder physicalDataRoot = physicalFs.folder("d");
|
||||
Assert.assertFalse(masterkeyFile.exists());
|
||||
Assert.assertFalse(masterkeyBkupFile.exists());
|
||||
Assert.assertFalse(physicalDataRoot.exists());
|
||||
|
||||
// init crypto fs:
|
||||
final FileSystem fs = new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo");
|
||||
Assert.assertTrue(masterkeyFile.exists());
|
||||
Assert.assertTrue(masterkeyBkupFile.exists());
|
||||
fs.create();
|
||||
Assert.assertTrue(physicalDataRoot.exists());
|
||||
Assert.assertEquals(3, physicalFs.children().count()); // d + masterkey.cryptomator + masterkey.cryptomator.bkup
|
||||
Assert.assertEquals(1, physicalDataRoot.files().count()); // ROOT file
|
||||
Assert.assertEquals(1, physicalDataRoot.folders().count()); // ROOT directory
|
||||
}
|
||||
|
||||
@Test(timeout = 1000)
|
||||
public void testMasterkeyBackupBehaviour() throws InterruptedException {
|
||||
// mock cryptor:
|
||||
final Cryptor cryptor = new NoCryptor();
|
||||
|
||||
// some mock fs:
|
||||
final FileSystem physicalFs = new InMemoryFileSystem();
|
||||
final File masterkeyBkupFile = physicalFs.file("masterkey.cryptomator.bkup");
|
||||
Assert.assertFalse(masterkeyBkupFile.exists());
|
||||
|
||||
// first initialization:
|
||||
new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo");
|
||||
Assert.assertTrue(masterkeyBkupFile.exists());
|
||||
final Instant bkupDateT0 = masterkeyBkupFile.lastModified();
|
||||
|
||||
// make sure some time passes, as the resolution of last modified date
|
||||
// is not in nanos:
|
||||
Thread.sleep(1);
|
||||
|
||||
// second initialization:
|
||||
new CryptoFileSystem(physicalFs, cryptor, Mockito.mock(CryptoFileSystemDelegate.class), "foo");
|
||||
Assert.assertTrue(masterkeyBkupFile.exists());
|
||||
final Instant bkupDateT1 = masterkeyBkupFile.lastModified();
|
||||
|
||||
Assert.assertTrue(bkupDateT1.isAfter(bkupDateT0));
|
||||
}
|
||||
|
||||
@Test(timeout = 1000)
|
||||
public void testDirectoryCreation() throws UncheckedIOException, IOException {
|
||||
// mock stuff and prepare crypto FS:
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.cryptomator.crypto.engine.Cryptor;
|
||||
import org.cryptomator.crypto.engine.impl.CryptorImpl;
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.crypto.CryptoFileSystem;
|
||||
@@ -50,11 +51,17 @@ class FileSystemFactories implements Iterable<FileSystemFactory> {
|
||||
}
|
||||
|
||||
private FileSystem createCryptoFileSystemInMemory() {
|
||||
return new CryptoFileSystem(createInMemoryFileSystem(), new CryptorImpl(RANDOM_MOCK), Mockito.mock(CryptoFileSystemDelegate.class), "aPassphrase");
|
||||
return new CryptoFileSystem(createInMemoryFileSystem(), createCryptor(), Mockito.mock(CryptoFileSystemDelegate.class), "aPassphrase");
|
||||
}
|
||||
|
||||
private FileSystem createCryptoFileSystemNio() {
|
||||
return new CryptoFileSystem(createNioFileSystem(), new CryptorImpl(RANDOM_MOCK), Mockito.mock(CryptoFileSystemDelegate.class), "aPassphrase");
|
||||
return new CryptoFileSystem(createNioFileSystem(), createCryptor(), Mockito.mock(CryptoFileSystemDelegate.class), "aPassphrase");
|
||||
}
|
||||
|
||||
private Cryptor createCryptor() {
|
||||
Cryptor cryptor = new CryptorImpl(RANDOM_MOCK);
|
||||
cryptor.randomizeMasterkey();
|
||||
return cryptor;
|
||||
}
|
||||
|
||||
private void add(String name, FileSystemFactory factory) {
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
package org.cryptomator.ui.controllers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -96,80 +99,38 @@ public class ChangePasswordController extends AbstractFXMLViewController {
|
||||
|
||||
@FXML
|
||||
private void didClickChangePasswordButton(ActionEvent event) {
|
||||
throw new UnsupportedOperationException("TODO");
|
||||
/*
|
||||
* downloadsPageLink.setVisible(false);
|
||||
* final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
|
||||
* final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
|
||||
*
|
||||
* // decrypt with old password:
|
||||
* final CharSequence oldPassword = oldPasswordField.getCharacters();
|
||||
* try (final InputStream masterKeyInputStream = Files.newInputStream(masterKeyPath, StandardOpenOption.READ)) {
|
||||
* vault.getCryptor().decryptMasterKey(masterKeyInputStream, oldPassword);
|
||||
* Files.copy(masterKeyPath, masterKeyBackupPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
* } catch (IOException ex) {
|
||||
* messageText.setText(resourceBundle.getString("changePassword.errorMessage.decryptionFailed"));
|
||||
* LOG.error("Decryption failed for technical reasons.", ex);
|
||||
* newPasswordField.swipe();
|
||||
* retypePasswordField.swipe();
|
||||
* return;
|
||||
* } catch (WrongPasswordException e) {
|
||||
* messageText.setText(resourceBundle.getString("changePassword.errorMessage.wrongPassword"));
|
||||
* newPasswordField.swipe();
|
||||
* retypePasswordField.swipe();
|
||||
* Platform.runLater(oldPasswordField::requestFocus);
|
||||
* return;
|
||||
* } catch (UnsupportedKeyLengthException ex) {
|
||||
* messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedKeyLengthInstallJCE"));
|
||||
* LOG.warn("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
|
||||
* newPasswordField.swipe();
|
||||
* retypePasswordField.swipe();
|
||||
* return;
|
||||
* } catch (UnsupportedVaultException e) {
|
||||
* downloadsPageLink.setVisible(true);
|
||||
* if (e.isVaultOlderThanSoftware()) {
|
||||
* messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
|
||||
* } else if (e.isSoftwareOlderThanVault()) {
|
||||
* messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
|
||||
* }
|
||||
* newPasswordField.swipe();
|
||||
* retypePasswordField.swipe();
|
||||
* return;
|
||||
* } finally {
|
||||
* oldPasswordField.swipe();
|
||||
* }
|
||||
*
|
||||
* // when we reach this line, decryption was successful.
|
||||
*
|
||||
* // encrypt with new password:
|
||||
* final CharSequence newPassword = newPasswordField.getCharacters();
|
||||
* try (final OutputStream masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.SYNC)) {
|
||||
* vault.getCryptor().encryptMasterKey(masterKeyOutputStream, newPassword);
|
||||
* messageText.setText(resourceBundle.getString("changePassword.infoMessage.success"));
|
||||
* Platform.runLater(this::didChangePassword);
|
||||
* // At this point the backup is still using the old password.
|
||||
* // It will be changed as soon as the user unlocks the vault the next time.
|
||||
* // This way he can still restore the old password, if he doesn't remember the new one.
|
||||
* } catch (IOException ex) {
|
||||
* LOG.error("Re-encryption failed for technical reasons. Restoring Backup.", ex);
|
||||
* this.restoreBackupQuietly();
|
||||
* } finally {
|
||||
* newPasswordField.swipe();
|
||||
* retypePasswordField.swipe();
|
||||
* }
|
||||
*/
|
||||
}
|
||||
|
||||
private void restoreBackupQuietly() {
|
||||
/*
|
||||
* final Path masterKeyPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_FILE);
|
||||
* final Path masterKeyBackupPath = vault.getPath().resolve(Vault.VAULT_MASTERKEY_BACKUP_FILE);
|
||||
* try {
|
||||
* Files.copy(masterKeyBackupPath, masterKeyPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
* } catch (IOException ex) {
|
||||
* LOG.error("Restoring Backup failed.", ex);
|
||||
* }
|
||||
*/
|
||||
downloadsPageLink.setVisible(false);
|
||||
try {
|
||||
vault.changePassphrase(oldPasswordField.getCharacters(), newPasswordField.getCharacters());
|
||||
messageText.setText(resourceBundle.getString("changePassword.infoMessage.success"));
|
||||
Platform.runLater(this::didChangePassword);
|
||||
} catch (InvalidPassphraseException e) {
|
||||
messageText.setText(resourceBundle.getString("changePassword.errorMessage.wrongPassword"));
|
||||
newPasswordField.swipe();
|
||||
retypePasswordField.swipe();
|
||||
Platform.runLater(oldPasswordField::requestFocus);
|
||||
return;
|
||||
} catch (IOException ex) {
|
||||
messageText.setText(resourceBundle.getString("changePassword.errorMessage.decryptionFailed"));
|
||||
LOG.error("Decryption failed for technical reasons.", ex);
|
||||
newPasswordField.swipe();
|
||||
retypePasswordField.swipe();
|
||||
return;
|
||||
// } catch (UnsupportedVaultException e) {
|
||||
// downloadsPageLink.setVisible(true);
|
||||
// if (e.isVaultOlderThanSoftware()) {
|
||||
// messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
|
||||
// } else if (e.isSoftwareOlderThanVault()) {
|
||||
// messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
|
||||
// }
|
||||
// newPasswordField.swipe();
|
||||
// retypePasswordField.swipe();
|
||||
// return;
|
||||
} finally {
|
||||
oldPasswordField.swipe();
|
||||
newPasswordField.swipe();
|
||||
retypePasswordField.swipe();
|
||||
}
|
||||
}
|
||||
|
||||
private void didChangePassword() {
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.util.Set;
|
||||
import org.apache.commons.lang3.CharUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.cryptomator.common.Optionals;
|
||||
import org.cryptomator.crypto.engine.InvalidPassphraseException;
|
||||
import org.cryptomator.filesystem.FileSystem;
|
||||
import org.cryptomator.filesystem.crypto.CryptoFileSystemDelegate;
|
||||
import org.cryptomator.filesystem.crypto.CryptoFileSystemFactory;
|
||||
@@ -90,7 +91,16 @@ public class Vault implements Serializable, CryptoFileSystemDelegate {
|
||||
if (fs.children().count() > 0) {
|
||||
throw new FileAlreadyExistsException(null, null, "Vault location not empty.");
|
||||
}
|
||||
cryptoFileSystemFactory.get(fs, passphrase, this);
|
||||
cryptoFileSystemFactory.initializeNew(fs, passphrase);
|
||||
} catch (UncheckedIOException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void changePassphrase(CharSequence oldPassphrase, CharSequence newPassphrase) throws IOException, InvalidPassphraseException {
|
||||
try {
|
||||
FileSystem fs = NioFileSystem.rootedAt(path);
|
||||
cryptoFileSystemFactory.changePassphrase(fs, oldPassphrase, newPassphrase);
|
||||
} catch (UncheckedIOException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
@@ -99,7 +109,7 @@ public class Vault implements Serializable, CryptoFileSystemDelegate {
|
||||
public synchronized void activateFrontend(CharSequence passphrase) throws FrontendCreationFailedException {
|
||||
try {
|
||||
FileSystem fs = NioFileSystem.rootedAt(path);
|
||||
FileSystem cryptoFs = cryptoFileSystemFactory.get(fs, passphrase, this);
|
||||
FileSystem cryptoFs = cryptoFileSystemFactory.unlockExisting(fs, passphrase, this);
|
||||
String contextPath = StringUtils.prependIfMissing(mountName, "/");
|
||||
Frontend frontend = frontendFactory.get().create(cryptoFs, contextPath);
|
||||
filesystemFrontend = closer.closeLater(frontend);
|
||||
@@ -165,10 +175,6 @@ public class Vault implements Serializable, CryptoFileSystemDelegate {
|
||||
return StringUtils.removeEnd(path.getFileName().toString(), VAULT_FILE_EXTENSION);
|
||||
}
|
||||
|
||||
// public Cryptor getCryptor() {
|
||||
// return cryptor;
|
||||
// }
|
||||
|
||||
public ObjectProperty<Boolean> unlockedProperty() {
|
||||
return unlocked;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user