Skip to content

Commit

Permalink
Merge pull request #16599 from iterate-ch/bugfix/SDS-1621-cache
Browse files Browse the repository at this point in the history
Cache passphrase to avoid expensive unlock operation.
  • Loading branch information
dkocher authored Nov 29, 2024
2 parents a2a33bb + b6d7253 commit 8f4f056
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -42,7 +41,6 @@

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -92,7 +90,6 @@ protected List<UserFileKeySetRequest> operate(final PasswordCallback callback) t
final List<UserFileKeySetRequest> 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<UserKeyPairContainer, Credentials> passphrases = new HashMap<>();
UserFileKeySetBatchRequest request;
do {
log.debug("Request a list of missing file keys limited to {}", fileId);
Expand All @@ -105,11 +102,7 @@ protected List<UserFileKeySetRequest> 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()),
Expand Down
105 changes: 85 additions & 20 deletions dracoon/src/main/java/ch/cyberduck/core/sds/SDSSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand All @@ -124,6 +116,9 @@ public class SDSSession extends HttpSession<SDSApiClient> {
private final ExpiringObjectHolder<UserKeyPairContainer> keyPairDeprecated
= new ExpiringObjectHolder<>(preferences.getLong("sds.encryption.keys.ttl"));

private final ExpiringObjectHolder<Credentials> keyPairPassphrase
= new ExpiringObjectHolder<>(preferences.getLong("sds.encryption.keys.ttl"));

private final ExpiringObjectHolder<SystemDefaults> systemDefaults
= new ExpiringObjectHolder<>(preferences.getLong("sds.useracount.ttl"));

Expand All @@ -136,6 +131,14 @@ public class SDSSession extends HttpSession<SDSApiClient> {
private final ExpiringObjectHolder<SoftwareVersionData> softwareVersion
= new ExpiringObjectHolder<>(preferences.getLong("sds.useracount.ttl"));

private final LRUCache<KeyPairCacheReference, Credentials> keyPairPassphrases
= LRUCache.build(new RemovalListener<KeyPairCacheReference, Credentials>() {
@Override
public void onRemoval(final RemovalNotification<KeyPairCacheReference, Credentials> notification) {
//
}
}, 2, preferences.getLong("sds.encryption.keys.ttl"), false);

private UserKeyPair.Version requiredKeyPairVersion;

private final SDSNodeIdProvider nodeid = new SDSNodeIdProvider(this);
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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 {
Expand All @@ -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());
Expand All @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 8f4f056

Please sign in to comment.