diff --git a/ssh/pom.xml b/ssh/pom.xml
index 20ed47375f4..7b3086f1bbb 100644
--- a/ssh/pom.xml
+++ b/ssh/pom.xml
@@ -45,9 +45,9 @@
${project.version}
- com.hierynomus
+ ch.iterate.ssh
sshj
- ${sshj-version}
+ 0.39.1
com.jcraft
@@ -60,7 +60,7 @@
${jsch-agentproxy-version}
- net.schmizz
+ com.hierynomus
sshj
diff --git a/ssh/src/main/java/ch/cyberduck/core/sftp/auth/SFTPAgentAuthentication.java b/ssh/src/main/java/ch/cyberduck/core/sftp/auth/SFTPAgentAuthentication.java
index f7dd92c4a3e..874f1617865 100644
--- a/ssh/src/main/java/ch/cyberduck/core/sftp/auth/SFTPAgentAuthentication.java
+++ b/ssh/src/main/java/ch/cyberduck/core/sftp/auth/SFTPAgentAuthentication.java
@@ -80,13 +80,20 @@ public Boolean authenticate(final Host bookmark, final LoginCallback prompt, fin
final Credentials configuration = new OpenSSHCredentialsConfigurator().configure(bookmark);
if(configuration.isPublicKeyAuthentication()) {
try {
- final Local identity = configuration.getIdentity();
+ final Local setting = configuration.getIdentity();
if(log.isWarnEnabled()) {
- log.warn(String.format("Only read specific key %s from SSH agent with IdentitiesOnly configuration", identity));
+ log.warn(String.format("Only read specific key %s from SSH agent with IdentitiesOnly configuration", setting));
+ }
+ final Identity identity = isPrivateKey(setting) ?
+ identityFromPrivateKey(setting) :
+ identityFromPublicKey(setting);
+ if(identity != null) {
+ identities = Collections.singleton(identity);
+ }
+ else {
+ log.warn(String.format("Missing public key for %s", setting));
+ identities = Collections.emptyList();
}
- identities = this.isPrivateKey(identity) ?
- this.identityFromPrivateKey(identity) :
- this.identityFromPublicKey(identity);
}
catch(IOException e) {
throw new DefaultIOExceptionMappingService().map(e);
@@ -143,32 +150,33 @@ protected Collection filter(final Credentials credentials, final Colle
return identities;
}
- private boolean isPrivateKey(final Local identity) throws AccessDeniedException, IOException {
+ static boolean isPrivateKey(final Local identity) throws AccessDeniedException, IOException {
final KeyFormat format = KeyProviderUtil.detectKeyFileFormat(
new InputStreamReader(identity.getInputStream()), true);
return format != KeyFormat.Unknown;
}
- private Collection identityFromPrivateKey(final Local identity) throws IOException, AccessDeniedException {
+ static Identity identityFromPrivateKey(final Local identity) throws IOException, AccessDeniedException {
final File pubKey = OpenSSHKeyFileUtil.getPublicKeyFile(new File(identity.getAbsolute()));
if(pubKey != null) {
- return this.identityFromPublicKey(LocalFactory.get(pubKey.getAbsolutePath()));
+ return identityFromPublicKey(LocalFactory.get(pubKey.getAbsolutePath()));
}
log.warn(String.format("Unable to find public key file for identity %s", identity));
- return Collections.emptyList();
+ return null;
}
- private Collection identityFromPublicKey(final Local identity) throws IOException, AccessDeniedException {
+ static Identity identityFromPublicKey(final Local identity) throws IOException, AccessDeniedException {
final List lines = IOUtils.readLines(identity.getInputStream(), Charset.defaultCharset());
for(String line : lines) {
final String keydata = line.trim();
if(StringUtils.isNotBlank(keydata)) {
String[] parts = keydata.split("\\s+");
if(parts.length >= 2) {
- return Collections.singletonList(new CustomIdentity(Base64.decodeBase64(parts[1])));
+ return new CustomIdentity(Base64.decodeBase64(parts[1]));
}
}
}
- throw new IOException(String.format("Failure reading public key %s", identity));
+ log.warn(String.format("Failure reading public key %s", identity));
+ return null;
}
}
\ No newline at end of file
diff --git a/ssh/src/main/java/ch/cyberduck/core/sftp/auth/SFTPPublicKeyAuthentication.java b/ssh/src/main/java/ch/cyberduck/core/sftp/auth/SFTPPublicKeyAuthentication.java
index e8307a75f7f..7850ace8e62 100644
--- a/ssh/src/main/java/ch/cyberduck/core/sftp/auth/SFTPPublicKeyAuthentication.java
+++ b/ssh/src/main/java/ch/cyberduck/core/sftp/auth/SFTPPublicKeyAuthentication.java
@@ -19,6 +19,7 @@
import ch.cyberduck.core.Credentials;
import ch.cyberduck.core.Host;
import ch.cyberduck.core.Local;
+import ch.cyberduck.core.LocalFactory;
import ch.cyberduck.core.LocaleFactory;
import ch.cyberduck.core.LoginCallback;
import ch.cyberduck.core.LoginOptions;
@@ -32,11 +33,13 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicBoolean;
+import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyFileUtil;
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.userauth.keyprovider.FileKeyProvider;
@@ -65,32 +68,53 @@ public Boolean authenticate(final Host bookmark, final LoginCallback prompt, fin
if(log.isDebugEnabled()) {
log.debug(String.format("Login using public key authentication with credentials %s", credentials));
}
- final Local identity = credentials.getIdentity();
+ final Local privKey = credentials.getIdentity();
+ final Local pubKey;
final FileKeyProvider provider;
final AtomicBoolean canceled = new AtomicBoolean();
try {
final KeyFormat format = KeyProviderUtil.detectKeyFileFormat(
- new InputStreamReader(identity.getInputStream(), StandardCharsets.UTF_8), true);
+ new InputStreamReader(privKey.getInputStream(), StandardCharsets.UTF_8), true);
if(log.isInfoEnabled()) {
- log.info(String.format("Reading private key %s with key format %s", identity, format));
+ log.info(String.format("Reading private key %s with key format %s", privKey, format));
}
switch(format) {
case PKCS8:
provider = new PKCS8KeyFile.Factory().create();
+ pubKey = null;
break;
- case OpenSSH:
+ case OpenSSH: {
provider = new OpenSSHKeyFile.Factory().create();
+ final File f = OpenSSHKeyFileUtil.getPublicKeyFile(new File(privKey.getAbsolute()));
+ if(f != null) {
+ pubKey = LocalFactory.get(f.getAbsolutePath());
+ }
+ else {
+ pubKey = null;
+ }
break;
- case OpenSSHv1:
+ }
+ case OpenSSHv1: {
provider = new OpenSSHKeyV1KeyFile.Factory().create();
+ final File f = OpenSSHKeyFileUtil.getPublicKeyFile(new File(privKey.getAbsolute()));
+ if(f != null) {
+ pubKey = LocalFactory.get(f.getAbsolutePath());
+ }
+ else {
+ pubKey = null;
+ }
break;
+ }
case PuTTY:
provider = new PuTTYKeyFile.Factory().create();
+ pubKey = null;
break;
default:
- throw new InteroperabilityException(String.format("Unknown key format for file %s", identity.getName()));
+ throw new InteroperabilityException(String.format("Unknown key format for file %s", privKey.getName()));
}
- provider.init(new InputStreamReader(identity.getInputStream(), StandardCharsets.UTF_8), new PasswordFinder() {
+ provider.init(new InputStreamReader(privKey.getInputStream(), StandardCharsets.UTF_8),
+ pubKey != null ? new InputStreamReader(pubKey.getInputStream(), StandardCharsets.UTF_8) : null,
+ new PasswordFinder() {
@Override
public char[] reqPassword(Resource> resource) {
if(StringUtils.isEmpty(credentials.getIdentityPassphrase())) {
@@ -100,7 +124,7 @@ public char[] reqPassword(Resource> resource) {
LocaleFactory.localizedString("Private key password protected", "Credentials"),
String.format("%s (%s)",
LocaleFactory.localizedString("Enter the passphrase for the private key file", "Credentials"),
- identity.getAbbreviatedPath()),
+ privKey.getAbbreviatedPath()),
new LoginOptions()
.icon(bookmark.getProtocol().disk())
.user(false).password(true)