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)