Skip to content

Commit

Permalink
Cache passphrase to avoid expensive unlock operation.
Browse files Browse the repository at this point in the history
  • Loading branch information
ylangisc committed Nov 29, 2024
1 parent a2a33bb commit 2cce2bd
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,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 +91,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 +103,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 = 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()),
Expand Down
10 changes: 5 additions & 5 deletions dracoon/src/main/java/ch/cyberduck/core/sds/SDSSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -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<Credentials> 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) {
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 = new TripleCryptKeyPair(session.getHost()).unlock(callback, userKeyPair);
}
catch(LoginCanceledException e) {
throw new AccessDeniedException(LocaleFactory.localizedString("Decryption password required", "SDS"), e);
Expand Down

0 comments on commit 2cce2bd

Please sign in to comment.