Skip to content

Commit

Permalink
Add parameter for master key file.
Browse files Browse the repository at this point in the history
  • Loading branch information
dkocher committed Nov 28, 2017
1 parent 36da235 commit c6f8bf7
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 45 deletions.
Expand Up @@ -94,18 +94,18 @@ public Vault find(final Session session, final Path file, final boolean lookup)
if(lookup) {
final LoadingVaultLookupListener listener = new LoadingVaultLookupListener(session, this, prompt);
if(file.attributes().getVault() != null) {
return this.find(session, file, listener);
return this.find(session, file.attributes().getVault(), listener);
}
final Path directory = file.getParent();
if(directory.attributes().getVault() != null) {
return this.find(session, directory, listener);
return this.find(session, directory.attributes().getVault(), listener);
}
}
return Vault.DISABLED;
}

protected Vault find(final Session<?> session, final Path directory, final LoadingVaultLookupListener listener) throws VaultUnlockCancelException {
final Vault vault = VaultFactory.get(directory.attributes().getVault(), keychain);
final Vault vault = VaultFactory.get(directory, file, keychain);
listener.found(vault);
return vault;
}
Expand Down
10 changes: 5 additions & 5 deletions core/src/main/java/ch/cyberduck/core/vault/VaultFactory.java
Expand Up @@ -35,25 +35,25 @@ protected VaultFactory() {
super("factory.vault.class");
}

public static Vault get(final Path directory, final PasswordStore keychain) {
return new VaultFactory().create(directory, keychain);
public static Vault get(final Path directory, final Path masterkey, final PasswordStore keychain) {
return new VaultFactory().create(directory, masterkey, keychain);
}

private Vault create(final Path directory, final PasswordStore keychain) {
private Vault create(final Path directory, final Path masterkey, final PasswordStore keychain) {
final String clazz = PreferencesFactory.get().getProperty("factory.vault.class");
if(null == clazz) {
throw new FactoryException(String.format("No implementation given for factory %s", this.getClass().getSimpleName()));
}
try {
final Class<Vault> name = (Class<Vault>) Class.forName(clazz);
final Constructor<Vault> constructor = ConstructorUtils.getMatchingAccessibleConstructor(name,
directory.getClass(), keychain.getClass());
directory.getClass(), masterkey.getClass(), keychain.getClass());
if(null == constructor) {
log.warn(String.format("No matching constructor for parameter %s", directory.getClass()));
// Call default constructor for disabled implementations
return name.newInstance();
}
return constructor.newInstance(directory, keychain);
return constructor.newInstance(directory, masterkey, keychain);
}
catch(InstantiationException | InvocationTargetException | ClassNotFoundException | IllegalAccessException e) {
log.error(String.format("Failure loading callback class %s. %s", clazz, e.getMessage()));
Expand Down
Expand Up @@ -49,7 +49,7 @@ public void visit(final AttributedList<Path> list, final int index, final Path f
if(log.isInfoEnabled()) {
log.info(String.format("Found master key %s", file));
}
final Vault vault = VaultFactory.get(directory, keychain);
final Vault vault = VaultFactory.get(directory, file, keychain);
if(vault.equals(Vault.DISABLED)) {
return;
}
Expand Down
Expand Up @@ -61,11 +61,12 @@ public boolean find(final Path file) throws BackgroundException {
final Vault vault = registry.find(session, file);
if(vault.equals(Vault.DISABLED)) {
if(preferences.getBoolean("cryptomator.enable") && preferences.getBoolean("cryptomator.vault.autodetect")) {
if(proxy.withCache(cache).find(new Path(file.getParent(), MASTERKEY_FILE_NAME, EnumSet.of(Path.Type.file)))) {
final Path key = new Path(file.getParent(), MASTERKEY_FILE_NAME, EnumSet.of(Path.Type.file));
if(proxy.withCache(cache).find(key)) {
if(log.isInfoEnabled()) {
log.info(String.format("Found master key %s", file));
log.info(String.format("Found master key %s", key));
}
final Vault cryptomator = VaultFactory.get(file.getParent(), keychain);
final Vault cryptomator = VaultFactory.get(file.getParent(), key, keychain);
if(!cryptomator.equals(Vault.DISABLED)) {
lookup.found(cryptomator);
if(log.isInfoEnabled()) {
Expand Down
Expand Up @@ -68,8 +68,8 @@ public class CryptoVault implements Vault {
Security.insertProviderAt(provider, position);
}

private static final String MASTERKEY_FILE_NAME = "masterkey.cryptomator";
private static final String BACKUPKEY_FILE_NAME = "masterkey.cryptomator.bkup";
private static final String DEFAULT_MASTERKEY_FILE_NAME = "masterkey.cryptomator";
private static final String DEFAULT_BACKUPKEY_FILE_NAME = "masterkey.cryptomator.bkup";

private static final int VAULT_VERSION = 6;

Expand All @@ -81,6 +81,7 @@ public class CryptoVault implements Vault {
* Root of vault directory
*/
private final Path home;
private final Path masterkey;

private final Preferences preferences = PreferencesFactory.get();

Expand All @@ -90,7 +91,12 @@ public class CryptoVault implements Vault {
private final CryptoDirectoryProvider directoryProvider;

public CryptoVault(final Path home, final PasswordStore keychain) {
this(home, new Path(home, DEFAULT_MASTERKEY_FILE_NAME, EnumSet.of(Path.Type.file, Path.Type.vault)), keychain);
}

public CryptoVault(final Path home, final Path masterkey, final PasswordStore keychain) {
this.home = home;
this.masterkey = masterkey;
this.keychain = keychain;
// New vault home with vault flag set for internal use
final EnumSet<AbstractPath.Type> type = EnumSet.copyOf(home.getType());
Expand All @@ -103,23 +109,22 @@ public CryptoVault(final Path home, final PasswordStore keychain) {
@Override
public synchronized Path create(final Session<?> session, final String region, final VaultCredentials credentials) throws BackgroundException {
final CryptorProvider provider = new Version1CryptorModule().provideCryptorProvider(
FastSecureRandomProvider.get().provide()
FastSecureRandomProvider.get().provide()
);
final Path masterKeyFile = new Path(home, MASTERKEY_FILE_NAME, EnumSet.of(Path.Type.file, Path.Type.vault));
final Host bookmark = session.getHost();
if(credentials.isSaved()) {
keychain.addPassword(String.format("Cryptomator Passphrase %s", bookmark.getHostname()),
new DefaultUrlProvider(bookmark).toUrl(masterKeyFile).find(DescriptiveUrl.Type.provider).getUrl(), credentials.getPassword());
new DefaultUrlProvider(bookmark).toUrl(masterkey).find(DescriptiveUrl.Type.provider).getUrl(), credentials.getPassword());
}
final String passphrase = credentials.getPassword();
final KeyFile masterKeyFileContent = provider.createNew().writeKeysToMasterkeyFile(passphrase, VAULT_VERSION);
if(log.isDebugEnabled()) {
log.debug(String.format("Write master key to %s", masterKeyFile));
log.debug(String.format("Write master key to %s", masterkey));
}
// Obtain non encrypted directory writer
final Directory directory = session._getFeature(Directory.class);
final Path vault = directory.mkdir(home, region, new TransferStatus());
new ContentWriter(session).write(masterKeyFile, masterKeyFileContent.serialize());
new ContentWriter(session).write(masterkey, masterKeyFileContent.serialize());
this.open(KeyFile.parse(masterKeyFileContent.serialize()), passphrase);
final Path secondLevel = directoryProvider.toEncrypted(session, home.attributes().getDirectoryId(), home);
final Path firstLevel = secondLevel.getParent();
Expand All @@ -139,11 +144,10 @@ public synchronized CryptoVault load(final Session<?> session, final PasswordCal
log.warn(String.format("Skip unlock of open vault %s", this));
return this;
}
final Path masterKeyFile = new Path(home, MASTERKEY_FILE_NAME, EnumSet.of(Path.Type.file, Path.Type.vault));
if(log.isDebugEnabled()) {
log.debug(String.format("Attempt to read master key from %s", masterKeyFile));
log.debug(String.format("Attempt to read master key from %s", masterkey));
}
final String json = new ContentReader(session).read(masterKeyFile);
final String json = new ContentReader(session).read(masterkey);
if(log.isDebugEnabled()) {
log.debug(String.format("Read master key %s", json));
}
Expand All @@ -152,13 +156,13 @@ public synchronized CryptoVault load(final Session<?> session, final PasswordCal
masterKeyFileContent = KeyFile.parse(json.getBytes());
}
catch(JsonParseException | IllegalArgumentException | IllegalStateException e) {
throw new VaultException(String.format("Failure reading vault master key file %s", masterKeyFile.getName()), e);
throw new VaultException(String.format("Failure reading vault master key file %s", masterkey.getName()), e);
}
final Host bookmark = session.getHost();
final String passphrase = keychain.getPassword(String.format("Cryptomator Passphrase %s", bookmark.getHostname()),
new DefaultUrlProvider(bookmark).toUrl(masterKeyFile).find(DescriptiveUrl.Type.provider).getUrl());
this.unlock(session, masterKeyFile, masterKeyFileContent, passphrase, bookmark, prompt,
MessageFormat.format(LocaleFactory.localizedString("Provide your passphrase to unlock the Cryptomator Vault “{0}“", "Cryptomator"), home.getName()));
new DefaultUrlProvider(bookmark).toUrl(masterkey).find(DescriptiveUrl.Type.provider).getUrl());
this.unlock(session, masterkey, masterKeyFileContent, passphrase, bookmark, prompt,
MessageFormat.format(LocaleFactory.localizedString("Provide your passphrase to unlock the Cryptomator Vault “{0}“", "Cryptomator"), home.getName()));
home.attributes().setVault(home);
return this;
}
Expand All @@ -169,13 +173,13 @@ private void unlock(final Session<?> session, final Path masterKeyFile, final Ke
if(null == passphrase) {
credentials = prompt.prompt(
bookmark, LocaleFactory.localizedString("Unlock Vault", "Cryptomator"),
message,
new LoginOptions()
.save(preferences.getBoolean("vault.keychain"))
.user(false)
.anonymous(false)
.icon("cryptomator.tiff")
.passwordPlaceholder(LocaleFactory.localizedString("Passphrase", "Cryptomator")));
message,
new LoginOptions()
.save(preferences.getBoolean("vault.keychain"))
.user(false)
.anonymous(false)
.icon("cryptomator.tiff")
.passwordPlaceholder(LocaleFactory.localizedString("Passphrase", "Cryptomator")));
if(null == credentials.getPassword()) {
throw new LoginCanceledException();
}
Expand All @@ -192,16 +196,16 @@ private void unlock(final Session<?> session, final Path masterKeyFile, final Ke
}
// Save password with hostname and path to masterkey.cryptomator in keychain
keychain.addPassword(String.format("Cryptomator Passphrase %s", bookmark.getHostname()),
new DefaultUrlProvider(bookmark).toUrl(masterKeyFile).find(DescriptiveUrl.Type.provider).getUrl(), credentials.getPassword());
new DefaultUrlProvider(bookmark).toUrl(masterKeyFile).find(DescriptiveUrl.Type.provider).getUrl(), credentials.getPassword());
// Save masterkey.cryptomator content in preferences
preferences.setProperty(new DefaultUrlProvider(bookmark).toUrl(masterKeyFile).find(DescriptiveUrl.Type.provider).getUrl(),
new String(masterKeyFileContent.serialize()));
new String(masterKeyFileContent.serialize()));
}
}
catch(CryptoAuthenticationException e) {
this.unlock(session, masterKeyFile, masterKeyFileContent, null, bookmark,
prompt, String.format("%s %s.", e.getDetail(),
MessageFormat.format(LocaleFactory.localizedString("Provide your passphrase to unlock the Cryptomator Vault “{0}“", "Cryptomator"), home.getName())));
prompt, String.format("%s %s.", e.getDetail(),
MessageFormat.format(LocaleFactory.localizedString("Provide your passphrase to unlock the Cryptomator Vault “{0}“", "Cryptomator"), home.getName())));
}
}

Expand Down Expand Up @@ -232,18 +236,18 @@ private KeyFile upgrade(final Session<?> session, final KeyFile keyFile, final C
log.warn(String.format("Upgrade vault version %d to %d", keyFile.getVersion(), VAULT_VERSION));
try {
final CryptorProvider provider = new Version1CryptorModule().provideCryptorProvider(
FastSecureRandomProvider.get().provide()
FastSecureRandomProvider.get().provide()
);
final Cryptor cryptor = provider.createFromKeyFile(keyFile, passphrase, keyFile.getVersion());
// Create backup, as soon as we know the password was correct
final Path masterKeyFileBackup = new Path(home, BACKUPKEY_FILE_NAME, EnumSet.of(Path.Type.file, Path.Type.vault));
final Path masterKeyFileBackup = new Path(home, DEFAULT_BACKUPKEY_FILE_NAME, EnumSet.of(Path.Type.file, Path.Type.vault));
new ContentWriter(session).write(masterKeyFileBackup, keyFile.serialize());
if(log.isInfoEnabled()) {
log.info(String.format("Master key backup saved in %s", masterKeyFileBackup));
}
// Write updated masterkey file
final KeyFile upgradedMasterKeyFile = cryptor.writeKeysToMasterkeyFile(passphrase, VAULT_VERSION);
final Path masterKeyFile = new Path(home, MASTERKEY_FILE_NAME, EnumSet.of(Path.Type.file, Path.Type.vault));
final Path masterKeyFile = new Path(home, DEFAULT_MASTERKEY_FILE_NAME, EnumSet.of(Path.Type.file, Path.Type.vault));
final byte[] masterKeyFileContent = upgradedMasterKeyFile.serialize();
new ContentWriter(session).write(masterKeyFile, masterKeyFileContent, new TransferStatus().exists(true).length(masterKeyFileContent.length));
log.warn(String.format("Updated masterkey %s to version %d", masterKeyFile, VAULT_VERSION));
Expand All @@ -263,7 +267,7 @@ private KeyFile upgrade(final Session<?> session, final KeyFile keyFile, final C

private void open(final KeyFile keyFile, final CharSequence passphrase) throws VaultException, CryptoAuthenticationException {
final CryptorProvider provider = new Version1CryptorModule().provideCryptorProvider(
FastSecureRandomProvider.get().provide()
FastSecureRandomProvider.get().provide()
);
if(log.isDebugEnabled()) {
log.debug(String.format("Initialized crypto provider %s", provider));
Expand Down Expand Up @@ -380,7 +384,7 @@ public Path decrypt(final Session<?> session, final Path file) throws Background
final String ciphertext = m.group(1);
try {
final String cleartextFilename = cryptor.fileNameCryptor().decryptFilename(
ciphertext, file.getParent().attributes().getDirectoryId().getBytes(StandardCharsets.UTF_8));
ciphertext, file.getParent().attributes().getDirectoryId().getBytes(StandardCharsets.UTF_8));
final PathAttributes attributes = new PathAttributes(file.attributes());
if(inflated.getName().startsWith(DIR_PREFIX)) {
final Permission permission = attributes.getPermission();
Expand All @@ -405,12 +409,12 @@ public Path decrypt(final Session<?> session, final Path file) throws Background
}
catch(AuthenticationFailedException e) {
throw new CryptoAuthenticationException(
"Failure to decrypt due to an unauthentic ciphertext", e);
"Failure to decrypt due to an unauthentic ciphertext", e);
}
}
else {
throw new CryptoFilenameMismatchException(
String.format("Failure to decrypt due to missing pattern match for %s", BASE32_PATTERN));
String.format("Failure to decrypt due to missing pattern match for %s", BASE32_PATTERN));
}
}

Expand Down Expand Up @@ -470,7 +474,7 @@ public CryptoDirectoryProvider getDirectoryProvider() {

public int numberOfChunks(final long cleartextFileSize) {
return (int) (cleartextFileSize / cryptor.fileContentCryptor().cleartextChunkSize() +
((cleartextFileSize % cryptor.fileContentCryptor().cleartextChunkSize() > 0) ? 1 : 0));
((cleartextFileSize % cryptor.fileContentCryptor().cleartextChunkSize() > 0) ? 1 : 0));
}

@Override
Expand Down

0 comments on commit c6f8bf7

Please sign in to comment.