diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMissingFileKeysSchedulerFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMissingFileKeysSchedulerFeature.java index a2df13f8a4a..1d7eb658de6 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMissingFileKeysSchedulerFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMissingFileKeysSchedulerFeature.java @@ -33,7 +33,6 @@ import ch.cyberduck.core.sds.io.swagger.client.model.UserUserPublicKey; import ch.cyberduck.core.sds.triplecrypt.TripleCryptConverter; import ch.cyberduck.core.sds.triplecrypt.TripleCryptExceptionMappingService; -import ch.cyberduck.core.sds.triplecrypt.TripleCryptKeyPair; import ch.cyberduck.core.shared.ThreadPoolSchedulerFeature; import org.apache.commons.lang3.StringUtils; @@ -103,7 +102,7 @@ protected List operate(final PasswordCallback callback) t for(FileFileKeys fileKey : files.get(item.getFileId())) { final EncryptedFileKey encryptedFileKey = TripleCryptConverter.toCryptoEncryptedFileKey(fileKey.getFileKeyContainer()); final UserKeyPairContainer keyPairForDecryption = session.getKeyPairForFileKey(encryptedFileKey.getVersion()); - final Credentials passphrase = new TripleCryptKeyPair(session.getHost()).unlock(callback, TripleCryptConverter.toCryptoUserKeyPair(keyPairForDecryption)); + final Credentials passphrase = session.unlockTripleCryptKeyPair(callback, TripleCryptConverter.toCryptoUserKeyPair(keyPairForDecryption)); for(UserUserPublicKey userPublicKey : userPublicKeys.get(item.getUserId())) { final EncryptedFileKey fk = this.encryptFileKey( TripleCryptConverter.toCryptoUserPrivateKey(keyPairForDecryption.getPrivateKeyContainer()), diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSSession.java b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSSession.java index 3af43de0166..c4b27d4ee96 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSSession.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSSession.java @@ -15,20 +15,8 @@ * GNU General Public License for more details. */ -import ch.cyberduck.core.ConnectionTimeoutFactory; -import ch.cyberduck.core.Credentials; -import ch.cyberduck.core.DefaultIOExceptionMappingService; -import ch.cyberduck.core.ExpiringObjectHolder; -import ch.cyberduck.core.Host; -import ch.cyberduck.core.HostKeyCallback; -import ch.cyberduck.core.HostUrlProvider; -import ch.cyberduck.core.ListService; -import ch.cyberduck.core.LocaleFactory; -import ch.cyberduck.core.LoginCallback; -import ch.cyberduck.core.PreferencesUseragentProvider; -import ch.cyberduck.core.Scheme; -import ch.cyberduck.core.UrlProvider; -import ch.cyberduck.core.Version; +import ch.cyberduck.core.*; +import ch.cyberduck.core.cache.LRUCache; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.InteroperabilityException; import ch.cyberduck.core.exception.LoginCanceledException; @@ -100,6 +88,8 @@ import com.dracoon.sdk.crypto.error.UnknownVersionException; import com.dracoon.sdk.crypto.model.EncryptedFileKey; import com.dracoon.sdk.crypto.model.UserKeyPair; +import com.google.common.cache.RemovalListener; +import com.google.common.cache.RemovalNotification; import static ch.cyberduck.core.oauth.OAuth2AuthorizationService.CYBERDUCK_REDIRECT_URI; @@ -124,6 +114,9 @@ public class SDSSession extends HttpSession { private final ExpiringObjectHolder keyPairDeprecated = new ExpiringObjectHolder<>(preferences.getLong("sds.encryption.keys.ttl")); + private final ExpiringObjectHolder keyPairPassphrase + = new ExpiringObjectHolder<>(preferences.getLong("sds.encryption.keys.ttl")); + private final ExpiringObjectHolder systemDefaults = new ExpiringObjectHolder<>(preferences.getLong("sds.useracount.ttl")); @@ -136,6 +129,14 @@ public class SDSSession extends HttpSession { private final ExpiringObjectHolder softwareVersion = new ExpiringObjectHolder<>(preferences.getLong("sds.useracount.ttl")); + private final LRUCache keyPairPassphrases + = LRUCache.build(new RemovalListener() { + @Override + public void onRemoval(final RemovalNotification notification) { + // + } + }, 2, preferences.getLong("sds.encryption.keys.ttl"), false); + private UserKeyPair.Version requiredKeyPairVersion; private final SDSNodeIdProvider nodeid = new SDSNodeIdProvider(this); @@ -188,7 +189,7 @@ public void process(final HttpRequest request, final HttpContext context) throws } configuration.setServiceUnavailableRetryStrategy(new CustomServiceUnavailableRetryStrategy(host, - new ExecutionCountServiceUnavailableRetryStrategy(new PreconditionFailedResponseInterceptor(host, authorizationService, prompt), + new ExecutionCountServiceUnavailableRetryStrategy(new PreconditionFailedResponseInterceptor(host, authorizationService, prompt), new OAuth2ErrorResponseInterceptor(host, authorizationService)))); if(new HostPreferences(host).getBoolean("sds.limit.requests.enable")) { configuration.addInterceptorLast(new RateLimitingHttpRequestInterceptor(new DefaultHttpRateLimiter( @@ -315,6 +316,30 @@ private boolean isNewCryptoAvailable() throws BackgroundException { return false; } + public Credentials unlockTripleCryptKeyPair(final PasswordCallback callback, final UserKeyPair keypair) throws BackgroundException { + if(!keyPairPassphrases.contains(keypair)) { + try { + keyPairPassphrases.put(keypair, new TripleCryptKeyPair().unlock(callback, host, keypair)); + } + catch(CryptoException e) { + throw new TripleCryptExceptionMappingService().map(e); + } + } + return keyPairPassphrases.get(keypair); + } + + public Credentials unlockTripleCryptKeyPair(final PasswordCallback callback, final UserKeyPair keypair, final String passphrase) throws BackgroundException { + if(!keyPairPassphrases.contains(keypair)) { + try { + keyPairPassphrases.put(keypair, new TripleCryptKeyPair().unlock(callback, host, keypair, passphrase)); + } + catch(CryptoException e) { + throw new TripleCryptExceptionMappingService().map(e); + } + } + return keyPairPassphrases.get(keypair); + } + protected void unlockTripleCryptKeyPair(final LoginCallback prompt, final UserAccountWrapper user, final UserKeyPair.Version requiredKeyPairVersion) throws BackgroundException { try { @@ -336,14 +361,14 @@ protected void unlockTripleCryptKeyPair(final LoginCallback prompt, final UserAc final UserKeyPairContainer deprecated = new UserApi(client).requestUserKeyPair(StringUtils.EMPTY, UserKeyPair.Version.RSA2048.getValue(), null); final UserKeyPair keypair = TripleCryptConverter.toCryptoUserKeyPair(deprecated); log.debug("Attempt to unlock deprecated private key {}", keypair.getUserPrivateKey()); - deprecatedCredentials = new TripleCryptKeyPair(host).unlock(prompt, keypair); + deprecatedCredentials = this.unlockTripleCryptKeyPair(prompt, keypair); keyPairDeprecated.set(deprecated); } if(!migrated) { final UserKeyPairContainer deprecated = new UserApi(client).requestUserKeyPair(StringUtils.EMPTY, UserKeyPair.Version.RSA2048.getValue(), null); final UserKeyPair keypair = TripleCryptConverter.toCryptoUserKeyPair(deprecated); log.debug("Attempt to unlock and migrate deprecated private key {}", keypair.getUserPrivateKey()); - deprecatedCredentials = new TripleCryptKeyPair(host).unlock(prompt, keypair); + deprecatedCredentials = this.unlockTripleCryptKeyPair(prompt, keypair); final UserKeyPair newPair = Crypto.generateUserKeyPair(requiredKeyPairVersion, deprecatedCredentials.getPassword().toCharArray()); final CreateKeyPairRequest request = new CreateKeyPairRequest(); request.setPreviousPrivateKey(deprecated.getPrivateKeyContainer()); @@ -361,15 +386,15 @@ protected void unlockTripleCryptKeyPair(final LoginCallback prompt, final UserAc if(deprecatedCredentials != null) { log.debug("Attempt to unlock private key with passphrase from deprecated private key {}", keypair.getUserPrivateKey()); if(Crypto.checkUserKeyPair(keypair, deprecatedCredentials.getPassword().toCharArray())) { - new TripleCryptKeyPair(host).unlock(prompt, keypair, deprecatedCredentials.getPassword()); + this.unlockTripleCryptKeyPair(prompt, keypair, deprecatedCredentials.getPassword()); } else { - new TripleCryptKeyPair(host).unlock(prompt, keypair); + this.unlockTripleCryptKeyPair(prompt, keypair); } } else { log.debug("Attempt to unlock private key {}", keypair.getUserPrivateKey()); - new TripleCryptKeyPair(host).unlock(prompt, keypair); + this.unlockTripleCryptKeyPair(prompt, keypair); } } catch(CryptoException e) { diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSShareFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSShareFeature.java index 0e71858e124..6cf3422b72e 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSShareFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSShareFeature.java @@ -38,7 +38,6 @@ import ch.cyberduck.core.sds.io.swagger.client.model.UserKeyPairContainer; import ch.cyberduck.core.sds.triplecrypt.TripleCryptConverter; import ch.cyberduck.core.sds.triplecrypt.TripleCryptExceptionMappingService; -import ch.cyberduck.core.sds.triplecrypt.TripleCryptKeyPair; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; @@ -119,7 +118,7 @@ public DescriptiveUrl toDownloadUrl(final Path file, final Sharee sharee, Create final EncryptedFileKey encFileKey = TripleCryptConverter.toCryptoEncryptedFileKey(key); final UserKeyPairContainer keyPairContainer = session.getKeyPairForFileKey(encFileKey.getVersion()); final UserKeyPair userKeyPair = TripleCryptConverter.toCryptoUserKeyPair(keyPairContainer); - final Credentials passphrase = new TripleCryptKeyPair(bookmark).unlock(callback, userKeyPair); + final Credentials passphrase = session.unlockTripleCryptKeyPair(callback, userKeyPair); final PlainFileKey plainFileKey = Crypto.decryptFileKey(encFileKey, userKeyPair.getUserPrivateKey(), passphrase.getPassword().toCharArray()); // encrypt file key with a new key pair diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/triplecrypt/TripleCryptKeyPair.java b/dracoon/src/main/java/ch/cyberduck/core/sds/triplecrypt/TripleCryptKeyPair.java index dc2d028a461..3f90764d550 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/triplecrypt/TripleCryptKeyPair.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/triplecrypt/TripleCryptKeyPair.java @@ -17,7 +17,6 @@ import ch.cyberduck.core.Credentials; import ch.cyberduck.core.DescriptiveUrl; -import ch.cyberduck.core.ExpiringObjectHolder; import ch.cyberduck.core.Host; import ch.cyberduck.core.HostPasswordStore; import ch.cyberduck.core.LocaleFactory; @@ -28,7 +27,6 @@ import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.LocalAccessDeniedException; import ch.cyberduck.core.exception.LoginCanceledException; -import ch.cyberduck.core.preferences.HostPreferences; import ch.cyberduck.core.shared.DefaultUrlProvider; import ch.cyberduck.core.vault.VaultCredentials; @@ -47,57 +45,45 @@ public class TripleCryptKeyPair { private final HostPasswordStore keychain = PasswordStoreFactory.get(); - private final Host host; - - private final ExpiringObjectHolder cache; - - public TripleCryptKeyPair(final Host host) { - this.host = host; - this.cache = new ExpiringObjectHolder<>(new HostPreferences(host).getLong("sds.encryption.keys.ttl")); - } - - public Credentials unlock(final PasswordCallback callback, final UserKeyPair keypair) throws CryptoException, BackgroundException { - final String passphrase = keychain.getPassword(toServiceName(host, keypair.getUserPublicKey().getVersion()), toAccountName(host)); - return this.unlock(callback, keypair, passphrase); + public Credentials unlock(final PasswordCallback callback, final Host bookmark, final UserKeyPair keypair) throws CryptoException, BackgroundException { + final String passphrase = keychain.getPassword(toServiceName(bookmark, keypair.getUserPublicKey().getVersion()), toAccountName(bookmark)); + return this.unlock(callback, bookmark, keypair, passphrase); } - public Credentials unlock(final PasswordCallback callback, final UserKeyPair keypair, final String passphrase) throws CryptoException, LoginCanceledException { - return this.unlock(callback, keypair, passphrase, LocaleFactory.localizedString("Enter your decryption password to access encrypted data rooms.", "SDS")); + public Credentials unlock(final PasswordCallback callback, final Host bookmark, final UserKeyPair keypair, final String passphrase) throws CryptoException, LoginCanceledException { + return this.unlock(callback, bookmark, keypair, passphrase, LocaleFactory.localizedString("Enter your decryption password to access encrypted data rooms.", "SDS")); } - private Credentials unlock(final PasswordCallback callback, final UserKeyPair keypair, String passphrase, final String message) throws LoginCanceledException, CryptoException { - if(cache.get() == null) { - final Credentials credentials; - if(null == passphrase) { - credentials = callback.prompt(host, LocaleFactory.localizedString("Decryption password required", "SDS"), message, - new LoginOptions() - .icon(host.getProtocol().disk()) - ); - if(credentials.getPassword() == null) { - throw new LoginCanceledException(); - } - } - else { - credentials = new VaultCredentials(passphrase).withSaved(false); + private Credentials unlock(final PasswordCallback callback, final Host bookmark, final UserKeyPair keypair, String passphrase, final String message) throws LoginCanceledException, CryptoException { + final Credentials credentials; + if(null == passphrase) { + credentials = callback.prompt(bookmark, LocaleFactory.localizedString("Decryption password required", "SDS"), message, + new LoginOptions() + .icon(bookmark.getProtocol().disk()) + ); + if(credentials.getPassword() == null) { + throw new LoginCanceledException(); } - if(!Crypto.checkUserKeyPair(keypair, credentials.getPassword().toCharArray())) { - return this.unlock(callback, keypair, null, String.format("%s. %s", LocaleFactory.localizedString("Invalid passphrase", "Credentials"), LocaleFactory.localizedString("Enter your decryption password to access encrypted data rooms.", "SDS"))); - } - else { - if(credentials.isSaved()) { - log.info("Save encryption password for {}", host); - try { - keychain.addPassword(toServiceName(host, keypair.getUserPublicKey().getVersion()), - toAccountName(host), credentials.getPassword()); - } - catch(LocalAccessDeniedException e) { - log.error("Failure {} saving credentials for {} in password store", e, host); - } + } + else { + credentials = new VaultCredentials(passphrase).withSaved(false); + } + if(!Crypto.checkUserKeyPair(keypair, credentials.getPassword().toCharArray())) { + return this.unlock(callback, bookmark, keypair, null, String.format("%s. %s", LocaleFactory.localizedString("Invalid passphrase", "Credentials"), LocaleFactory.localizedString("Enter your decryption password to access encrypted data rooms.", "SDS"))); + } + else { + if(credentials.isSaved()) { + log.info("Save encryption password for {}", bookmark); + try { + keychain.addPassword(toServiceName(bookmark, keypair.getUserPublicKey().getVersion()), + toAccountName(bookmark), credentials.getPassword()); + } + catch(LocalAccessDeniedException e) { + log.error("Failure {} saving credentials for {} in password store", e, bookmark); } - cache.set(credentials); } + return credentials; } - return cache.get(); } protected static String toServiceName(final Host bookmark, final UserKeyPair.Version version) { diff --git a/dracoon/src/main/java/ch/cyberduck/core/sds/triplecrypt/TripleCryptReadFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/triplecrypt/TripleCryptReadFeature.java index 200023a9727..012a3995ba9 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/triplecrypt/TripleCryptReadFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/triplecrypt/TripleCryptReadFeature.java @@ -92,7 +92,7 @@ public InputStream read(final Path file, final TransferStatus status, final Conn private Credentials unlock(final ConnectionCallback callback, final UserKeyPair userKeyPair) throws CryptoException, BackgroundException { final Credentials passphrase; try { - passphrase = new TripleCryptKeyPair(session.getHost()).unlock(callback, userKeyPair); + passphrase = session.unlockTripleCryptKeyPair(callback, userKeyPair); } catch(LoginCanceledException e) { throw new AccessDeniedException(LocaleFactory.localizedString("Decryption password required", "SDS"), e);