Skip to content

Commit

Permalink
Merge pull request #12927 from iterate-ch/feature/GH-12880-review
Browse files Browse the repository at this point in the history
Refactor Windows support for OpenSSH agent.
  • Loading branch information
dkocher authored Mar 2, 2022
2 parents 1a679ef + 6a31752 commit c3a6238
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 77 deletions.
48 changes: 29 additions & 19 deletions ssh/src/main/java/ch/cyberduck/core/sftp/SFTPSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import ch.cyberduck.core.sftp.openssh.OpenSSHIdentityAgentConfigurator;
import ch.cyberduck.core.sftp.openssh.OpenSSHJumpHostConfigurator;
import ch.cyberduck.core.sftp.openssh.OpenSSHPreferredAuthenticationsConfigurator;
import ch.cyberduck.core.sftp.openssh.WindowsOpenSSHAgentAuthenticator;
import ch.cyberduck.core.sftp.putty.PageantAuthenticator;
import ch.cyberduck.core.ssl.X509KeyManager;
import ch.cyberduck.core.ssl.X509TrustManager;
Expand All @@ -66,6 +67,7 @@
import java.util.LinkedHashMap;
import java.util.List;

import com.jcraft.jsch.agentproxy.AgentProxyException;
import net.schmizz.concurrent.Promise;
import net.schmizz.keepalive.KeepAlive;
import net.schmizz.keepalive.KeepAliveProvider;
Expand Down Expand Up @@ -119,9 +121,9 @@ protected SSHClient connect(final Proxy proxy, final HostKeyCallback key, final
final DefaultConfig configuration = new DefaultConfig();
if("zlib".equals(preferences.getProperty("ssh.compression"))) {
configuration.setCompressionFactories(Arrays.asList(
new DelayedZlibCompression.Factory(),
new ZlibCompression.Factory(),
new NoneCompression.Factory()));
new DelayedZlibCompression.Factory(),
new ZlibCompression.Factory(),
new NoneCompression.Factory()));
}
else {
configuration.setCompressionFactories(Collections.singletonList(new NoneCompression.Factory()));
Expand Down Expand Up @@ -163,7 +165,7 @@ protected SSHClient connect(final HostKeyCallback key, final LoginCallback promp
}
}
final DirectConnection tunnel = hop.newDirectConnection(
new OpenSSHHostnameConfigurator().getHostname(host.getHostname()), host.getPort());
new OpenSSHHostnameConfigurator().getHostname(host.getHostname()), host.getPort());
// Connect to internal host
connection.connectVia(tunnel);
}
Expand Down Expand Up @@ -245,13 +247,13 @@ public boolean alert(final ConnectionCallback prompt) throws BackgroundException

private void alert(final ConnectionCallback prompt, final String algorithm) throws ConnectionCanceledException {
prompt.warn(host, MessageFormat.format(LocaleFactory.localizedString("Insecure algorithm {0} negotiated with server", "Credentials"),
algorithm),
new StringAppender()
.append(LocaleFactory.localizedString("The algorithm is possibly too weak to meet current cryptography standards", "Credentials"))
.append(LocaleFactory.localizedString("Please contact your web hosting service provider for assistance", "Support")).toString(),
LocaleFactory.localizedString("Continue", "Credentials"),
LocaleFactory.localizedString("Disconnect", "Credentials"),
String.format("ssh.algorithm.whitelist.%s", host.getHostname()));
algorithm),
new StringAppender()
.append(LocaleFactory.localizedString("The algorithm is possibly too weak to meet current cryptography standards", "Credentials"))
.append(LocaleFactory.localizedString("Please contact your web hosting service provider for assistance", "Support")).toString(),
LocaleFactory.localizedString("Continue", "Credentials"),
LocaleFactory.localizedString("Disconnect", "Credentials"),
String.format("ssh.algorithm.whitelist.%s", host.getHostname()));
}

@Override
Expand Down Expand Up @@ -286,13 +288,21 @@ private void authenticate(final SSHClient client, final Host host, final LoginCa
switch(Factory.Platform.getDefault()) {
case windows:
defaultMethods.add(new SFTPAgentAuthentication(client, new PageantAuthenticator()));

defaultMethods.add(new SFTPAgentAuthentication(client, new OpenSSHAgentAuthenticator(
new OpenSSHIdentityAgentConfigurator().getIdentityAgent(host.getHostname()), true)));
try {
defaultMethods.add(new SFTPAgentAuthentication(client, new WindowsOpenSSHAgentAuthenticator()));
}
catch(AgentProxyException e) {
log.warn(String.format("Agent proxy failed with %s", e));
}
break;
default:
defaultMethods.add(new SFTPAgentAuthentication(client, new OpenSSHAgentAuthenticator(
new OpenSSHIdentityAgentConfigurator().getIdentityAgent(host.getHostname()), false)));
try {
defaultMethods.add(new SFTPAgentAuthentication(client, new OpenSSHAgentAuthenticator(
new OpenSSHIdentityAgentConfigurator().getIdentityAgent(host.getHostname()))));
}
catch(AgentProxyException e) {
log.warn(String.format("Agent proxy failed with %s", e));
}
break;
}
}
Expand Down Expand Up @@ -366,13 +376,13 @@ private void authenticate(final SSHClient client, final Host host, final LoginCa
}
catch(IllegalStateException ignored) {
log.warn(String.format("Server disconnected with %s while trying authentication method %s",
disconnectListener.getFailure(), auth));
disconnectListener.getFailure(), auth));
try {
if(null == disconnectListener.getFailure()) {
throw new ConnectionRefusedException(LocaleFactory.localizedString("Login failed", "Credentials"), ignored);
}
throw new SFTPExceptionMappingService().map(LocaleFactory.localizedString("Login failed", "Credentials"),
disconnectListener.getFailure());
disconnectListener.getFailure());
}
catch(InteroperabilityException e) {
throw new LoginFailureException(e.getDetail(false), e);
Expand All @@ -385,7 +395,7 @@ private void authenticate(final SSHClient client, final Host host, final LoginCa
if(!client.isAuthenticated()) {
if(null == lastFailure) {
throw new LoginFailureException(MessageFormat.format(LocaleFactory.localizedString(
"Login {0} with username and password", "Credentials"), BookmarkNameProvider.toString(host)));
"Login {0} with username and password", "Credentials"), BookmarkNameProvider.toString(host)));
}
throw lastFailure;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.RandomAccessFile;
import java.io.EOFException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
Expand All @@ -35,62 +32,19 @@
import com.jcraft.jsch.agentproxy.AgentProxyException;
import com.jcraft.jsch.agentproxy.Identity;
import com.jcraft.jsch.agentproxy.connector.SSHAgentConnector;
import com.jcraft.jsch.agentproxy.USocketFactory;
import com.jcraft.jsch.agentproxy.usocket.JNAUSocketFactory;

// Implements a wrapper around RandomAccessFile for use with jsch's
// SSH connector to support Windows' OpenSSH fork.
class RandomAccessFileSocketFactory implements USocketFactory
{
class WindowsSocket extends Socket
{
private RandomAccessFile raf;
WindowsSocket(String path) throws IOException
{
raf = new RandomAccessFile(path, "rw");
}

public int readFull(byte[] buf, int s, int len) throws IOException
{
try {
raf.readFully(buf, s, len);
} catch (EOFException e) {
return -1;
}
return len;
}
public void write(byte[] buf, int s, int len) throws IOException
{
raf.write(buf, s, len);
}
public void close() throws IOException
{
raf.close();
}
}

public Socket open(String path) throws IOException
{
return new WindowsSocket(path);
}
}

public class OpenSSHAgentAuthenticator extends AgentAuthenticator {
private static final Logger log = LogManager.getLogger(OpenSSHAgentAuthenticator.class);

private AgentProxy proxy;
private final AgentProxy proxy;

public OpenSSHAgentAuthenticator(final String socket, final boolean windows) {
try {
if (windows) {
proxy = new AgentProxy(new SSHAgentConnector(new RandomAccessFileSocketFactory(), "\\\\.\\pipe\\openssh-ssh-agent"));
} else {
proxy = new AgentProxy(new SSHAgentConnector(new JNAUSocketFactory(), socket));
}
}
catch(AgentProxyException e) {
log.warn(String.format("Agent proxy %s failed with %s", this, e));
}
public OpenSSHAgentAuthenticator(final String socket) throws AgentProxyException {
this(new AgentProxy(new SSHAgentConnector(new JNAUSocketFactory(), socket)));
}

public OpenSSHAgentAuthenticator(final AgentProxy proxy) {
this.proxy = proxy;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package ch.cyberduck.core.sftp.openssh;

/*
* Copyright (c) 2002-2022 iterate GmbH. All rights reserved.
* https://cyberduck.io/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

import java.io.EOFException;
import java.io.IOException;
import java.io.RandomAccessFile;

import com.jcraft.jsch.agentproxy.AgentProxy;
import com.jcraft.jsch.agentproxy.AgentProxyException;
import com.jcraft.jsch.agentproxy.USocketFactory;
import com.jcraft.jsch.agentproxy.connector.SSHAgentConnector;

public class WindowsOpenSSHAgentAuthenticator extends OpenSSHAgentAuthenticator {

public WindowsOpenSSHAgentAuthenticator() throws AgentProxyException {
super(new AgentProxy(new SSHAgentConnector(new RandomAccessFileSocketFactory(), "\\\\.\\pipe\\openssh-ssh-agent")));
}

/**
* Implements a wrapper around RandomAccessFile for use with jsch's SSH connector to support Windows' OpenSSH fork.
*/
private static class RandomAccessFileSocketFactory implements USocketFactory {
static class WindowsSocket extends Socket {
private final RandomAccessFile raf;

WindowsSocket(String path) throws IOException {
raf = new RandomAccessFile(path, "rw");
}

public int readFull(byte[] buf, int s, int len) throws IOException {
try {
raf.readFully(buf, s, len);
}
catch(EOFException e) {
return -1;
}
return len;
}

public void write(byte[] buf, int s, int len) throws IOException {
raf.write(buf, s, len);
}

public void close() throws IOException {
raf.close();
}
}

public Socket open(String path) throws IOException {
return new WindowsSocket(path);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,26 @@
* [email protected]
*/

import org.junit.Ignore;
import ch.cyberduck.core.Factory;

import org.apache.commons.lang3.StringUtils;
import org.junit.Test;

import java.util.Collection;

import com.jcraft.jsch.agentproxy.AgentProxyException;
import com.jcraft.jsch.agentproxy.Identity;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assume.assumeTrue;

public class OpenSSHAgentAuthenticatorTest {

@Test
@Ignore
public void testGetIdentities() {
final OpenSSHAgentAuthenticator authenticator = new OpenSSHAgentAuthenticator(null, false);
@Test(expected = AgentProxyException.class)
public void testGetIdentities() throws Exception {
assumeTrue(Factory.Platform.getDefault().equals(Factory.Platform.Name.mac));
final OpenSSHAgentAuthenticator authenticator = new OpenSSHAgentAuthenticator(StringUtils.EMPTY);
final Collection<Identity> identities = authenticator.getIdentities();
assertNotNull(authenticator.getProxy());
assertFalse(identities.isEmpty());
Expand Down

0 comments on commit c3a6238

Please sign in to comment.