Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache passphrase to avoid expensive unlock operation. #16599

Merged
merged 3 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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