From 2cce2bd0f9d9bb6f564e90d9cb43e50b69f36635 Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Fri, 29 Nov 2024 09:30:45 +0100 Subject: [PATCH 1/3] Cache passphrase to avoid expensive unlock operation. --- .../SDSMissingFileKeysSchedulerFeature.java | 8 +- .../ch/cyberduck/core/sds/SDSSession.java | 10 +-- .../cyberduck/core/sds/SDSShareFeature.java | 2 +- .../sds/triplecrypt/TripleCryptKeyPair.java | 76 +++++++++++-------- .../triplecrypt/TripleCryptReadFeature.java | 2 +- 5 files changed, 53 insertions(+), 45 deletions(-) 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..a2df13f8a4a 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMissingFileKeysSchedulerFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSMissingFileKeysSchedulerFeature.java @@ -42,7 +42,6 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -92,7 +91,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 +103,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 = new TripleCryptKeyPair(session.getHost()).unlock(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..3af43de0166 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSSession.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSSession.java @@ -336,14 +336,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 = new TripleCryptKeyPair(host).unlock(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 = new TripleCryptKeyPair(host).unlock(prompt, keypair); final UserKeyPair newPair = Crypto.generateUserKeyPair(requiredKeyPairVersion, deprecatedCredentials.getPassword().toCharArray()); final CreateKeyPairRequest request = new CreateKeyPairRequest(); request.setPreviousPrivateKey(deprecated.getPrivateKeyContainer()); @@ -361,15 +361,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()); + new TripleCryptKeyPair(host).unlock(prompt, keypair, deprecatedCredentials.getPassword()); } else { - new TripleCryptKeyPair().unlock(prompt, host, keypair); + new TripleCryptKeyPair(host).unlock(prompt, keypair); } } else { log.debug("Attempt to unlock private key {}", keypair.getUserPrivateKey()); - new TripleCryptKeyPair().unlock(prompt, host, keypair); + new TripleCryptKeyPair(host).unlock(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 135738eb426..0e71858e124 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSShareFeature.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSShareFeature.java @@ -119,7 +119,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 = new TripleCryptKeyPair(bookmark).unlock(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 3f90764d550..dc2d028a461 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,6 +17,7 @@ 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; @@ -27,6 +28,7 @@ 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; @@ -45,45 +47,57 @@ public class TripleCryptKeyPair { private final HostPasswordStore keychain = PasswordStoreFactory.get(); - 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); + 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 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")); + 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); } - 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(); - } - } - 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()); + 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")); + } + + 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(); } - catch(LocalAccessDeniedException e) { - log.error("Failure {} saving credentials for {} in password store", e, bookmark); + } + else { + credentials = new VaultCredentials(passphrase).withSaved(false); + } + 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); + } } + 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 7ff5aa46db1..200023a9727 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 = new TripleCryptKeyPair(session.getHost()).unlock(callback, userKeyPair); } catch(LoginCanceledException e) { throw new AccessDeniedException(LocaleFactory.localizedString("Decryption password required", "SDS"), e); From fd90c67a3e2df3b521d3740cd0b191b5995f38b5 Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Fri, 29 Nov 2024 10:28:05 +0100 Subject: [PATCH 2/3] Move unlocking to session. --- .../SDSMissingFileKeysSchedulerFeature.java | 3 +- .../ch/cyberduck/core/sds/SDSSession.java | 65 +++++++++++----- .../cyberduck/core/sds/SDSShareFeature.java | 3 +- .../sds/triplecrypt/TripleCryptKeyPair.java | 76 ++++++++----------- .../triplecrypt/TripleCryptReadFeature.java | 2 +- 5 files changed, 79 insertions(+), 70 deletions(-) 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); From b6d725306c5308d86f42f6e0ec76cf0c66b1fcc9 Mon Sep 17 00:00:00 2001 From: Yves Langisch Date: Fri, 29 Nov 2024 14:08:20 +0100 Subject: [PATCH 3/3] Wrap KeyPair and implement hashCode/equals. --- .../ch/cyberduck/core/sds/SDSSession.java | 58 ++++++++++++++++--- 1 file changed, 49 insertions(+), 9 deletions(-) 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 c4b27d4ee96..94715fc6431 100644 --- a/dracoon/src/main/java/ch/cyberduck/core/sds/SDSSession.java +++ b/dracoon/src/main/java/ch/cyberduck/core/sds/SDSSession.java @@ -56,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; @@ -129,10 +131,10 @@ public class SDSSession extends HttpSession { private final ExpiringObjectHolder softwareVersion = new ExpiringObjectHolder<>(preferences.getLong("sds.useracount.ttl")); - private final LRUCache keyPairPassphrases - = LRUCache.build(new RemovalListener() { + private final LRUCache keyPairPassphrases + = LRUCache.build(new RemovalListener() { @Override - public void onRemoval(final RemovalNotification notification) { + public void onRemoval(final RemovalNotification notification) { // } }, 2, preferences.getLong("sds.encryption.keys.ttl"), false); @@ -317,27 +319,29 @@ private boolean isNewCryptoAvailable() throws BackgroundException { } public Credentials unlockTripleCryptKeyPair(final PasswordCallback callback, final UserKeyPair keypair) throws BackgroundException { - if(!keyPairPassphrases.contains(keypair)) { + final KeyPairCacheReference reference = new KeyPairCacheReference(keypair); + if(!keyPairPassphrases.contains(reference)) { try { - keyPairPassphrases.put(keypair, new TripleCryptKeyPair().unlock(callback, host, keypair)); + keyPairPassphrases.put(reference, new TripleCryptKeyPair().unlock(callback, host, keypair)); } catch(CryptoException e) { throw new TripleCryptExceptionMappingService().map(e); } } - return keyPairPassphrases.get(keypair); + return keyPairPassphrases.get(reference); } public Credentials unlockTripleCryptKeyPair(final PasswordCallback callback, final UserKeyPair keypair, final String passphrase) throws BackgroundException { - if(!keyPairPassphrases.contains(keypair)) { + final KeyPairCacheReference reference = new KeyPairCacheReference(keypair); + if(!keyPairPassphrases.contains(reference)) { try { - keyPairPassphrases.put(keypair, new TripleCryptKeyPair().unlock(callback, host, keypair, passphrase)); + keyPairPassphrases.put(reference, new TripleCryptKeyPair().unlock(callback, host, keypair, passphrase)); } catch(CryptoException e) { throw new TripleCryptExceptionMappingService().map(e); } } - return keyPairPassphrases.get(keypair); + return keyPairPassphrases.get(reference); } protected void unlockTripleCryptKeyPair(final LoginCallback prompt, final UserAccountWrapper user, @@ -545,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();