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 2c0ec8574e7..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; @@ -42,7 +41,6 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -92,7 +90,6 @@ protected List operate(final PasswordCallback callback) t final List processed = new ArrayList<>(); // Null when operating from scheduler. File reference is set for post upload. final Long fileId = file != null ? Long.parseLong(nodeid.getVersionId(file)) : null; - final HashMap passphrases = new HashMap<>(); UserFileKeySetBatchRequest request; do { log.debug("Request a list of missing file keys limited to {}", fileId); @@ -105,11 +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()); - if(!passphrases.containsKey(keyPairForDecryption)) { - passphrases.put(keyPairForDecryption, - new TripleCryptKeyPair().unlock(callback, session.getHost(), TripleCryptConverter.toCryptoUserKeyPair(keyPairForDecryption))); - } - final Credentials passphrase = passphrases.get(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 7398be8d8e7..94715fc6431 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; @@ -68,6 +56,8 @@ import ch.cyberduck.core.threading.CancelCallback; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.http.HttpException; import org.apache.http.HttpHeaders; import org.apache.http.HttpRequest; @@ -100,6 +90,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 +116,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 +131,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 +191,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 +318,32 @@ private boolean isNewCryptoAvailable() throws BackgroundException { return false; } + public Credentials unlockTripleCryptKeyPair(final PasswordCallback callback, final UserKeyPair keypair) throws BackgroundException { + final KeyPairCacheReference reference = new KeyPairCacheReference(keypair); + if(!keyPairPassphrases.contains(reference)) { + try { + keyPairPassphrases.put(reference, new TripleCryptKeyPair().unlock(callback, host, keypair)); + } + catch(CryptoException e) { + throw new TripleCryptExceptionMappingService().map(e); + } + } + return keyPairPassphrases.get(reference); + } + + public Credentials unlockTripleCryptKeyPair(final PasswordCallback callback, final UserKeyPair keypair, final String passphrase) throws BackgroundException { + final KeyPairCacheReference reference = new KeyPairCacheReference(keypair); + if(!keyPairPassphrases.contains(reference)) { + try { + keyPairPassphrases.put(reference, new TripleCryptKeyPair().unlock(callback, host, keypair, passphrase)); + } + catch(CryptoException e) { + throw new TripleCryptExceptionMappingService().map(e); + } + } + return keyPairPassphrases.get(reference); + } + protected void unlockTripleCryptKeyPair(final LoginCallback prompt, final UserAccountWrapper user, final UserKeyPair.Version requiredKeyPairVersion) throws BackgroundException { try { @@ -336,14 +365,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().unlock(prompt, host, 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().unlock(prompt, host, 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 +390,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().unlock(prompt, host, keypair, deprecatedCredentials.getPassword()); + this.unlockTripleCryptKeyPair(prompt, keypair, deprecatedCredentials.getPassword()); } else { - new TripleCryptKeyPair().unlock(prompt, host, keypair); + this.unlockTripleCryptKeyPair(prompt, keypair); } } else { log.debug("Attempt to unlock private key {}", keypair.getUserPrivateKey()); - new TripleCryptKeyPair().unlock(prompt, host, keypair); + this.unlockTripleCryptKeyPair(prompt, keypair); } } catch(CryptoException e) { @@ -520,6 +549,42 @@ public UserKeyPair.Version requiredKeyPairVersion() { return requiredKeyPairVersion; } + private static class KeyPairCacheReference { + + private final UserKeyPair keypair; + + public KeyPairCacheReference(final UserKeyPair keypair) { + this.keypair = keypair; + } + + @Override + public boolean equals(final Object o) { + if(this == o) { + return true; + } + + if(o == null || getClass() != o.getClass()) { + return false; + } + + final KeyPairCacheReference that = (KeyPairCacheReference) o; + + return new EqualsBuilder().append(keypair.getUserPrivateKey().getVersion().getValue(), that.keypair.getUserPrivateKey().getVersion().getValue()). + append(keypair.getUserPrivateKey().getPrivateKey(), that.keypair.getUserPrivateKey().getPrivateKey()). + append(keypair.getUserPublicKey().getVersion().getValue(), that.keypair.getUserPublicKey().getVersion().getValue()). + append(keypair.getUserPrivateKey().getPrivateKey(), that.keypair.getUserPrivateKey().getPrivateKey()).isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(). + append(keypair.getUserPrivateKey().getVersion().getValue()). + append(keypair.getUserPrivateKey().getPrivateKey()). + append(keypair.getUserPublicKey().getVersion().getValue()). + append(keypair.getUserPrivateKey().getPrivateKey()).toHashCode(); + } + } + @Override protected void logout() { scheduler.shutdown(); 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 135738eb426..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().unlock(callback, bookmark, 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/TripleCryptReadFeature.java b/dracoon/src/main/java/ch/cyberduck/core/sds/triplecrypt/TripleCryptReadFeature.java index 7ff5aa46db1..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().unlock(callback, session.getHost(), userKeyPair); + passphrase = session.unlockTripleCryptKeyPair(callback, userKeyPair); } catch(LoginCanceledException e) { throw new AccessDeniedException(LocaleFactory.localizedString("Decryption password required", "SDS"), e);