0

我正在尝试设置一个简单的 SSH 客户端,该客户端使用 Apache Mina SSHD 通过已知主机文件验证服务器的公钥。我在下面拼凑出可行的伪代码 - 好吧,如果我知道如何让客户端等待服务器主机密钥验证完成,它就会起作用。

import java.io.IOException;
import java.net.SocketAddress;
import java.nio.file.*;
import java.security.PublicKey;
import java.util.concurrent.TimeUnit;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.config.hosts.KnownHostEntry;
import org.apache.sshd.client.future.*;
import org.apache.sshd.client.keyverifier.*;
import org.apache.sshd.client.session.ClientSession;

public class SshdTest {
    public static void main(String[] args) throws IOException {
        Path knownHostsPath = Paths.get(System.getProperty("user.home"), ".ssh", "known_hosts");
        SshClient ssh = SshClient.setUpDefaultClient();
        
        KnownHostsServerKeyVerifier verifier = new DefaultKnownHostsServerKeyVerifier(new ServerKeyVerifier() {
            @Override
            public boolean verifyServerKey(ClientSession cs, SocketAddress sa, PublicKey pk) {
                return true; // ask user to verify unknown public key
            }
        }, true, knownHostsPath);
        verifier.setModifiedServerKeyAcceptor(new ModifiedServerKeyAcceptor() {
            @Override
            public boolean acceptModifiedServerKey(ClientSession cs, SocketAddress sa, KnownHostEntry khe, PublicKey expected, PublicKey actual) throws Exception {
                return true; // ask user to verify public key change
            }
        });
        ssh.setServerKeyVerifier(verifier);
        
        ssh.start();
        
        String host = "localhost";
        String username = "user";
        int port = 22;        
        String password = "password";
        
        ConnectFuture cf = ssh.connect(username, host, port).verify(5L, TimeUnit.SECONDS);
        cf.await();
        
        // wait for server hostkey verification to complete and check ok?

        try ( 
            ClientSession session = cf.getSession()) {
            session.addPasswordIdentity(password);
            AuthFuture auth = session.auth();
            auth.await(5L, TimeUnit.SECONDS);
            
            // check ok and do the work
        }
        
        ssh.stop();
    }
}

我希望cf.await()行包含连接和主机密钥验证过程,但它没有,这意味着用户被要求同时验证密码和主机密钥,或者如果代码保持不变,则以某种未定义的顺序。

如何让客户端在尝试其他任何操作(例如用户身份验证)之前等待服务器主机密钥验证完成?另外,如何检查验证是否成功?

4

1 回答 1

0

最终我自己为此实现了同步,但不确定这是否是正确的方法。我怀疑像 Apache SSHD 这样的 API 不会提供实现相同功能的方法,但我一直没能找到它。

FWIW,伪代码如下。

import java.io.IOException;
import java.net.SocketAddress;
import java.nio.file.*;
import java.security.PublicKey;
import java.util.Objects;
import java.util.concurrent.*;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.config.hosts.KnownHostEntry;
import org.apache.sshd.client.future.*;
import org.apache.sshd.client.keyverifier.*;
import org.apache.sshd.client.session.ClientSession;

public class SshdTest {
    public static void main(String[] args) throws IOException {
        Path knownHostsPath = Paths.get(System.getProperty("user.home"), ".ssh", "known_hosts");
        SshClient ssh = SshClient.setUpDefaultClient();
        
        final BlockingQueue<Boolean> hostKeyVerification = new ArrayBlockingQueue<>(1);
        
        KnownHostsServerKeyVerifier verifier = new DefaultKnownHostsServerKeyVerifier(new ServerKeyVerifier() {
            @Override
            public boolean verifyServerKey(ClientSession cs, SocketAddress sa, PublicKey pk) {
                boolean ret = true;
                hostKeyVerification.offer(ret);
                return ret; // ask user to verify unknown public key
            }
        }, true, knownHostsPath) {
            @Override
            protected boolean acceptKnownHostEntry(ClientSession clientSession, SocketAddress remoteAddress, PublicKey serverKey, KnownHostEntry entry) {
                boolean ret = super.acceptKnownHostEntry(clientSession, remoteAddress, serverKey, entry);
                hostKeyVerification.offer(ret);
                return ret;
            }};
        verifier.setModifiedServerKeyAcceptor(new ModifiedServerKeyAcceptor() {
            @Override
            public boolean acceptModifiedServerKey(ClientSession cs, SocketAddress sa, KnownHostEntry khe, PublicKey expected, PublicKey actual) throws Exception {
                boolean ret = true;
                hostKeyVerification.offer(ret);
                return ret; // ask user to verify public key change
            }
        });
        ssh.setServerKeyVerifier(verifier);
        
        ssh.start();
        
        String host = "localhost";
        String username = "user";
        int port = 22;        
        String password = "password";
        
        ConnectFuture cf = ssh.connect(username, host, port).verify(5L, TimeUnit.SECONDS);
        cf.await();
        
        // wait for server hostkey verification to complete and check ok
        try {
            Boolean hostKeyVerified = hostKeyVerification.take();
            if (Objects.equals(hostKeyVerified, Boolean.FALSE)) {
                throw new IOException("failed to verify server host key");
            }
        } catch (InterruptedException ex) {
            throw new IOException("interrupted while waiting for hostkey verification process to complete");
        }

        try (ClientSession session = cf.getSession()) {
            session.addPasswordIdentity(password);
            AuthFuture auth = session.auth();
            auth.await(5L, TimeUnit.SECONDS);
            
            // check ok and do the work
        }
        
        ssh.stop();
    }
}
于 2020-07-08T08:08:43.177 回答