Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attempt to load public key when available #15254

Merged
merged 7 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
6 changes: 3 additions & 3 deletions ssh/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.hierynomus</groupId>
<groupId>ch.iterate.ssh</groupId>
<artifactId>sshj</artifactId>
<version>${sshj-version}</version>
<version>0.39.0</version>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
Expand All @@ -60,7 +60,7 @@
<version>${jsch-agentproxy-version}</version>
<exclusions>
<exclusion>
<groupId>net.schmizz</groupId>
<groupId>com.hierynomus</groupId>
<artifactId>sshj</artifactId>
</exclusion>
</exclusions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -143,32 +150,33 @@ protected Collection<Identity> 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<Identity> 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<Identity> identityFromPublicKey(final Local identity) throws IOException, AccessDeniedException {
static Identity identityFromPublicKey(final Local identity) throws IOException, AccessDeniedException {
final List<String> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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())) {
Expand All @@ -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)
Expand Down