Skip to content

Commit 8aefc36

Browse files
committed
Fail when reading private key for selected certificate fails.
1 parent 6405bc0 commit 8aefc36

File tree

5 files changed

+51
-27
lines changed

5 files changed

+51
-27
lines changed

core/src/main/java/ch/cyberduck/core/KeychainLoginService.java

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import ch.cyberduck.core.exception.LocalAccessDeniedException;
2323
import ch.cyberduck.core.exception.LoginCanceledException;
2424
import ch.cyberduck.core.exception.LoginFailureException;
25+
import ch.cyberduck.core.ssl.X509KeyManager;
2526
import ch.cyberduck.core.threading.CancelCallback;
2627

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

4647
@Override
47-
public void validate(final Host bookmark, final LoginCallback prompt, final LoginOptions options) throws ConnectionCanceledException, LoginFailureException {
48-
log.debug("Validate login credentials for {}", bookmark);
49-
final Credentials credentials = bookmark.getCredentials();
48+
public void validate(final Session<?> session, final LoginCallback prompt, final LoginOptions options) throws ConnectionCanceledException, LoginFailureException {
49+
log.debug("Validate login credentials for {}", session);
50+
final Host host = session.getHost();
51+
final Credentials credentials = host.getCredentials();
5052
if(credentials.isPublicKeyAuthentication()) {
5153
if(!credentials.getIdentity().attributes().getPermission().isReadable()) {
5254
log.warn("Prompt to select identity file not readable {}", credentials.getIdentity());
5355
credentials.setIdentity(prompt.select(credentials.getIdentity()));
5456
}
5557
}
5658
if(options.keychain) {
57-
log.debug("Lookup credentials in keychain for {}", bookmark);
59+
log.debug("Lookup credentials in keychain for {}", host);
5860
if(options.password) {
5961
if(StringUtils.isBlank(credentials.getPassword())) {
60-
final String password = keychain.findLoginPassword(bookmark);
62+
final String password = keychain.findLoginPassword(host);
6163
if(StringUtils.isNotBlank(password)) {
62-
log.info("Fetched password from keychain for {}", bookmark);
64+
log.info("Fetched password from keychain for {}", host);
6365
// No need to reinsert found password to the keychain.
6466
credentials.setPassword(password).setSaved(false);
6567
}
6668
}
6769
}
6870
if(options.token) {
6971
if(StringUtils.isBlank(credentials.getToken())) {
70-
final String token = keychain.findLoginToken(bookmark);
72+
final String token = keychain.findLoginToken(host);
7173
if(StringUtils.isNotBlank(token)) {
72-
log.info("Fetched token from keychain for {}", bookmark);
74+
log.info("Fetched token from keychain for {}", host);
7375
// No need to reinsert found token to the keychain.
7476
credentials.setToken(token).setSaved(false);
7577
}
7678
}
7779
}
7880
if(options.publickey) {
79-
final String passphrase = keychain.findPrivateKeyPassphrase(bookmark);
81+
final String passphrase = keychain.findPrivateKeyPassphrase(host);
8082
if(StringUtils.isNotBlank(passphrase)) {
81-
log.info("Fetched private key passphrase from keychain for {}", bookmark);
83+
log.info("Fetched private key passphrase from keychain for {}", host);
8284
// No need to reinsert found token to the keychain.
8385
credentials.setIdentityPassphrase(passphrase).setSaved(false);
8486
}
8587
}
8688
if(options.oauth) {
87-
final OAuthTokens tokens = keychain.findOAuthTokens(bookmark);
89+
final OAuthTokens tokens = keychain.findOAuthTokens(host);
8890
if(tokens.validate()) {
89-
log.info("Fetched OAuth tokens {} from keychain for {}", tokens, bookmark);
91+
log.info("Fetched OAuth tokens {} from keychain for {}", tokens, host);
9092
// No need to reinsert found token to the keychain.
9193
credentials.setOauth(tokens).setSaved(tokens.isExpired());
9294
}
9395
}
96+
if(options.certificate) {
97+
final String alias = host.getCredentials().getCertificate();
98+
if(StringUtils.isNotBlank(alias)) {
99+
final X509KeyManager manager = session.getFeature(X509KeyManager.class);
100+
if(manager != null) {
101+
if(null == manager.getPrivateKey(alias)) {
102+
log.warn("No private key found for alias {} in keychain", alias);
103+
throw new LoginFailureException(LocaleFactory.localizedString("Provide additional login credentials", "Credentials"));
104+
}
105+
}
106+
}
107+
}
94108
}
95-
if(!credentials.validate(bookmark.getProtocol(), options)) {
109+
if(!credentials.validate(host.getProtocol(), options)) {
96110
log.warn("Failed validation of credentials {} with options {}", credentials, options);
97-
final CredentialsConfigurator configurator = CredentialsConfiguratorFactory.get(bookmark.getProtocol());
111+
final CredentialsConfigurator configurator = CredentialsConfiguratorFactory.get(host.getProtocol());
98112
log.debug("Auto configure credentials with {}", configurator);
99-
final Credentials configuration = configurator.configure(bookmark);
100-
if(configuration.validate(bookmark.getProtocol(), options)) {
101-
bookmark.setCredentials(configuration);
102-
log.info("Auto configured credentials {} for {}", configuration, bookmark);
113+
final Credentials configuration = configurator.configure(host);
114+
if(configuration.validate(host.getProtocol(), options)) {
115+
host.setCredentials(configuration);
116+
log.info("Auto configured credentials {} for {}", configuration, host);
103117
return;
104118
}
105119
final StringAppender message = new StringAppender();
106120
if(options.password) {
107121
message.append(MessageFormat.format(LocaleFactory.localizedString(
108-
"Login {0} with username and password", "Credentials"), BookmarkNameProvider.toString(bookmark)));
122+
"Login {0} with username and password", "Credentials"), BookmarkNameProvider.toString(host)));
109123
}
110124
if(options.publickey) {
111125
message.append(LocaleFactory.localizedString(
112126
"Select the private key in PEM or PuTTY format", "Credentials"));
113127
}
114128
message.append(LocaleFactory.localizedString("No login credentials could be found in the Keychain", "Credentials"));
115-
this.prompt(bookmark, message.toString(), prompt, options);
129+
this.prompt(host, message.toString(), prompt, options);
116130
}
117131
log.debug("Validated credentials {} with options {}", credentials, options);
118132
}

core/src/main/java/ch/cyberduck/core/LoginConnectionService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public boolean check(final Session<?> session, final CancelCallback callback) th
9393
}
9494
// Obtain password from keychain or prompt
9595
synchronized(login) {
96-
login.validate(bookmark, prompt, new LoginOptions(bookmark.getProtocol()));
96+
login.validate(session, prompt, new LoginOptions(bookmark.getProtocol()));
9797
}
9898
this.connect(session, callback);
9999
return true;

core/src/main/java/ch/cyberduck/core/LoginService.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ public interface LoginService {
2727
/**
2828
* Obtain password from password store or prompt user for input
2929
*
30-
* @param bookmark Credentials
31-
* @param pompt Login prompt
32-
* @param options Login mechanism features
30+
* @param session Credentials
31+
* @param pompt Login prompt
32+
* @param options Login mechanism features
3333
*/
34-
void validate(Host bookmark, LoginCallback pompt, LoginOptions options) throws ConnectionCanceledException, LoginFailureException;
34+
void validate(Session<?> session, LoginCallback pompt, LoginOptions options) throws ConnectionCanceledException, LoginFailureException;
3535

3636
/**
3737
* Login and prompt on failure

core/src/main/java/ch/cyberduck/core/ssl/X509KeyManager.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
*/
2020

2121
import java.security.Principal;
22+
import java.security.PrivateKey;
2223
import java.security.cert.X509Certificate;
2324
import java.util.List;
2425

@@ -40,4 +41,13 @@ public interface X509KeyManager extends javax.net.ssl.X509KeyManager {
4041
* @param issuers Acceptable CA issuer subject names or null if it does not matter which issuers are used
4142
*/
4243
X509Certificate getCertificate(String alias, String[] keyTypes, Principal[] issuers);
44+
45+
/**
46+
* Find private key for certificate to use for authentication with mutual TLS
47+
*
48+
* @param alias Certificate alias
49+
* @return Null when not found
50+
*/
51+
@Override
52+
PrivateKey getPrivateKey(String alias);
4353
}

core/src/test/java/ch/cyberduck/core/KeychainLoginServiceTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ else if(1 == i) {
5151
@Test(expected = LoginCanceledException.class)
5252
public void testCancel() throws Exception {
5353
LoginService l = new KeychainLoginService(new DisabledPasswordStore());
54-
l.validate(new Host(new TestProtocol(), "h"), new DisabledLoginCallback(), new LoginOptions());
54+
l.validate(new NullSession(new Host(new TestProtocol(), "h")), new DisabledLoginCallback(), new LoginOptions());
5555
}
5656

5757
@Test
@@ -68,7 +68,7 @@ public String findLoginPassword(final Host bookmark) {
6868
final Credentials credentials = new Credentials();
6969
credentials.setUsername("u");
7070
final Host host = new Host(new TestProtocol(), "test.cyberduck.ch", credentials);
71-
l.validate(host, new DisabledLoginCallback(), new LoginOptions(host.getProtocol()));
71+
l.validate(new NullSession(host), new DisabledLoginCallback(), new LoginOptions(host.getProtocol()));
7272
assertTrue(keychain.get());
7373
assertFalse(host.getCredentials().isSaved());
7474
assertEquals("P", host.getCredentials().getPassword());

0 commit comments

Comments
 (0)