Skip to content
Open
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
54 changes: 34 additions & 20 deletions core/src/main/java/ch/cyberduck/core/KeychainLoginService.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import ch.cyberduck.core.exception.LocalAccessDeniedException;
import ch.cyberduck.core.exception.LoginCanceledException;
import ch.cyberduck.core.exception.LoginFailureException;
import ch.cyberduck.core.ssl.X509KeyManager;
import ch.cyberduck.core.threading.CancelCallback;

import org.apache.commons.lang3.StringUtils;
Expand All @@ -44,75 +45,88 @@ public KeychainLoginService(final HostPasswordStore keychain) {
}

@Override
public void validate(final Host bookmark, final LoginCallback prompt, final LoginOptions options) throws ConnectionCanceledException, LoginFailureException {
log.debug("Validate login credentials for {}", bookmark);
final Credentials credentials = bookmark.getCredentials();
public void validate(final Session<?> session, final LoginCallback prompt, final LoginOptions options) throws ConnectionCanceledException, LoginFailureException {
log.debug("Validate login credentials for {}", session);
final Host host = session.getHost();
final Credentials credentials = host.getCredentials();
if(credentials.isPublicKeyAuthentication()) {
if(!credentials.getIdentity().attributes().getPermission().isReadable()) {
log.warn("Prompt to select identity file not readable {}", credentials.getIdentity());
credentials.setIdentity(prompt.select(credentials.getIdentity()));
}
}
if(options.keychain) {
log.debug("Lookup credentials in keychain for {}", bookmark);
log.debug("Lookup credentials in keychain for {}", host);
if(options.password) {
if(StringUtils.isBlank(credentials.getPassword())) {
final String password = keychain.findLoginPassword(bookmark);
final String password = keychain.findLoginPassword(host);
if(StringUtils.isNotBlank(password)) {
log.info("Fetched password from keychain for {}", bookmark);
log.info("Fetched password from keychain for {}", host);
// No need to reinsert found password to the keychain.
credentials.setPassword(password).setSaved(false);
}
}
}
if(options.token) {
if(StringUtils.isBlank(credentials.getToken())) {
final String token = keychain.findLoginToken(bookmark);
final String token = keychain.findLoginToken(host);
if(StringUtils.isNotBlank(token)) {
log.info("Fetched token from keychain for {}", bookmark);
log.info("Fetched token from keychain for {}", host);
// No need to reinsert found token to the keychain.
credentials.setToken(token).setSaved(false);
}
}
}
if(options.publickey) {
final String passphrase = keychain.findPrivateKeyPassphrase(bookmark);
final String passphrase = keychain.findPrivateKeyPassphrase(host);
if(StringUtils.isNotBlank(passphrase)) {
log.info("Fetched private key passphrase from keychain for {}", bookmark);
log.info("Fetched private key passphrase from keychain for {}", host);
// No need to reinsert found token to the keychain.
credentials.setIdentityPassphrase(passphrase).setSaved(false);
}
}
if(options.oauth) {
final OAuthTokens tokens = keychain.findOAuthTokens(bookmark);
final OAuthTokens tokens = keychain.findOAuthTokens(host);
if(tokens.validate()) {
log.info("Fetched OAuth tokens {} from keychain for {}", tokens, bookmark);
log.info("Fetched OAuth tokens {} from keychain for {}", tokens, host);
// No need to reinsert found token to the keychain.
credentials.setOauth(tokens).setSaved(tokens.isExpired());
}
}
if(options.certificate) {
final String alias = host.getCredentials().getCertificate();
if(StringUtils.isNotBlank(alias)) {
final X509KeyManager manager = session.getFeature(X509KeyManager.class);
if(manager != null) {
if(null == manager.getPrivateKey(alias)) {
log.warn("No private key found for alias {} in keychain", alias);
throw new LoginFailureException(LocaleFactory.localizedString("Provide additional login credentials", "Credentials"));
}
}
}
}
}
if(!credentials.validate(bookmark.getProtocol(), options)) {
if(!credentials.validate(host.getProtocol(), options)) {
log.warn("Failed validation of credentials {} with options {}", credentials, options);
final CredentialsConfigurator configurator = CredentialsConfiguratorFactory.get(bookmark.getProtocol());
final CredentialsConfigurator configurator = CredentialsConfiguratorFactory.get(host.getProtocol());
log.debug("Auto configure credentials with {}", configurator);
final Credentials configuration = configurator.configure(bookmark);
if(configuration.validate(bookmark.getProtocol(), options)) {
bookmark.setCredentials(configuration);
log.info("Auto configured credentials {} for {}", configuration, bookmark);
final Credentials configuration = configurator.configure(host);
if(configuration.validate(host.getProtocol(), options)) {
host.setCredentials(configuration);
log.info("Auto configured credentials {} for {}", configuration, host);
return;
}
final StringAppender message = new StringAppender();
if(options.password) {
message.append(MessageFormat.format(LocaleFactory.localizedString(
"Login {0} with username and password", "Credentials"), BookmarkNameProvider.toString(bookmark)));
"Login {0} with username and password", "Credentials"), BookmarkNameProvider.toString(host)));
}
if(options.publickey) {
message.append(LocaleFactory.localizedString(
"Select the private key in PEM or PuTTY format", "Credentials"));
}
message.append(LocaleFactory.localizedString("No login credentials could be found in the Keychain", "Credentials"));
this.prompt(bookmark, message.toString(), prompt, options);
this.prompt(host, message.toString(), prompt, options);
}
log.debug("Validated credentials {} with options {}", credentials, options);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public boolean check(final Session<?> session, final CancelCallback callback) th
}
// Obtain password from keychain or prompt
synchronized(login) {
login.validate(bookmark, prompt, new LoginOptions(bookmark.getProtocol()));
login.validate(session, prompt, new LoginOptions(bookmark.getProtocol()));
}
this.connect(session, callback);
return true;
Expand Down
8 changes: 4 additions & 4 deletions core/src/main/java/ch/cyberduck/core/LoginService.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ public interface LoginService {
/**
* Obtain password from password store or prompt user for input
*
* @param bookmark Credentials
* @param pompt Login prompt
* @param options Login mechanism features
* @param session Credentials
* @param pompt Login prompt
* @param options Login mechanism features
*/
void validate(Host bookmark, LoginCallback pompt, LoginOptions options) throws ConnectionCanceledException, LoginFailureException;
void validate(Session<?> session, LoginCallback pompt, LoginOptions options) throws ConnectionCanceledException, LoginFailureException;

/**
* Login and prompt on failure
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/java/ch/cyberduck/core/ssl/X509KeyManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/

import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;

Expand All @@ -40,4 +41,13 @@ public interface X509KeyManager extends javax.net.ssl.X509KeyManager {
* @param issuers Acceptable CA issuer subject names or null if it does not matter which issuers are used
*/
X509Certificate getCertificate(String alias, String[] keyTypes, Principal[] issuers);

/**
* Find private key for certificate to use for authentication with mutual TLS
*
* @param alias Certificate alias
* @return Null when not found
*/
@Override
PrivateKey getPrivateKey(String alias);
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ else if(1 == i) {
@Test(expected = LoginCanceledException.class)
public void testCancel() throws Exception {
LoginService l = new KeychainLoginService(new DisabledPasswordStore());
l.validate(new Host(new TestProtocol(), "h"), new DisabledLoginCallback(), new LoginOptions());
l.validate(new NullSession(new Host(new TestProtocol(), "h")), new DisabledLoginCallback(), new LoginOptions());
}

@Test
Expand All @@ -68,7 +68,7 @@ public String findLoginPassword(final Host bookmark) {
final Credentials credentials = new Credentials();
credentials.setUsername("u");
final Host host = new Host(new TestProtocol(), "test.cyberduck.ch", credentials);
l.validate(host, new DisabledLoginCallback(), new LoginOptions(host.getProtocol()));
l.validate(new NullSession(host), new DisabledLoginCallback(), new LoginOptions(host.getProtocol()));
assertTrue(keychain.get());
assertFalse(host.getCredentials().isSaved());
assertEquals("P", host.getCredentials().getPassword());
Expand Down
Loading