feat: known_hosts (#206)

This commit is contained in:
hstyi
2025-02-12 11:45:55 +08:00
committed by GitHub
parent e5f854dfcd
commit 8a733379a3
7 changed files with 107 additions and 7 deletions

View File

@@ -6,8 +6,6 @@ import com.formdev.flatlaf.util.SystemInfo
import com.jetbrains.JBR import com.jetbrains.JBR
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.swing.Swing import kotlinx.coroutines.swing.Swing
import org.apache.commons.lang3.StringUtils
import org.jdesktop.swingx.JXLabel
import java.awt.BorderLayout import java.awt.BorderLayout
import java.awt.Component import java.awt.Component
import java.awt.Desktop import java.awt.Desktop
@@ -57,6 +55,7 @@ object OptionPane {
pane.selectInitialValue() pane.selectInitialValue()
} }
}) })
dialog.setLocationRelativeTo(parentComponent)
dialog.isVisible = true dialog.isVisible = true
dialog.dispose() dialog.dispose()
val selectedValue = pane.value val selectedValue = pane.value

View File

@@ -29,7 +29,7 @@ import org.apache.sshd.common.session.SessionListener.Event
import org.apache.sshd.common.util.net.SshdSocketAddress import org.apache.sshd.common.util.net.SshdSocketAddress
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.util.EventObject import java.util.*
import javax.swing.JComponent import javax.swing.JComponent
import javax.swing.SwingUtilities import javax.swing.SwingUtilities
@@ -87,9 +87,11 @@ class SSHTerminalTab(windowScope: WindowScope, host: Host) :
terminal.write("SSH client is opening...\r\n") terminal.write("SSH client is opening...\r\n")
} }
val owner = SwingUtilities.getWindowAncestor(terminalPanel)
val client = SshClients.openClient(host).also { sshClient = it } val client = SshClients.openClient(host).also { sshClient = it }
client.serverKeyVerifier = DialogServerKeyVerifier(owner)
// keyboard interactive // keyboard interactive
client.userInteraction = TerminalUserInteraction(SwingUtilities.getWindowAncestor(terminalPanel)) client.userInteraction = TerminalUserInteraction(owner)
val sessionListener = MySessionListener() val sessionListener = MySessionListener()
val channelListener = MyChannelListener() val channelListener = MyChannelListener()

View File

@@ -2,14 +2,20 @@ package app.termora
import app.termora.keymgr.OhKeyPairKeyPairProvider import app.termora.keymgr.OhKeyPairKeyPairProvider
import app.termora.terminal.TerminalSize import app.termora.terminal.TerminalSize
import org.apache.commons.lang3.StringUtils
import org.apache.sshd.client.ClientBuilder import org.apache.sshd.client.ClientBuilder
import org.apache.sshd.client.SshClient import org.apache.sshd.client.SshClient
import org.apache.sshd.client.channel.ChannelShell import org.apache.sshd.client.channel.ChannelShell
import org.apache.sshd.client.config.hosts.HostConfigEntryResolver import org.apache.sshd.client.config.hosts.HostConfigEntryResolver
import org.apache.sshd.client.config.hosts.KnownHostEntry
import org.apache.sshd.client.kex.DHGClient import org.apache.sshd.client.kex.DHGClient
import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier
import org.apache.sshd.client.keyverifier.ModifiedServerKeyAcceptor
import org.apache.sshd.client.keyverifier.ServerKeyVerifier
import org.apache.sshd.client.session.ClientSession import org.apache.sshd.client.session.ClientSession
import org.apache.sshd.common.SshException import org.apache.sshd.common.SshException
import org.apache.sshd.common.channel.PtyChannelConfiguration import org.apache.sshd.common.channel.PtyChannelConfiguration
import org.apache.sshd.common.config.keys.KeyUtils
import org.apache.sshd.common.global.KeepAliveHandler import org.apache.sshd.common.global.KeepAliveHandler
import org.apache.sshd.common.kex.BuiltinDHFactories import org.apache.sshd.common.kex.BuiltinDHFactories
import org.apache.sshd.common.keyprovider.KeyIdentityProvider import org.apache.sshd.common.keyprovider.KeyIdentityProvider
@@ -22,9 +28,16 @@ import org.eclipse.jgit.transport.CredentialsProvider
import org.eclipse.jgit.transport.sshd.IdentityPasswordProvider import org.eclipse.jgit.transport.sshd.IdentityPasswordProvider
import org.eclipse.jgit.transport.sshd.ProxyData import org.eclipse.jgit.transport.sshd.ProxyData
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.awt.Window
import java.net.InetSocketAddress import java.net.InetSocketAddress
import java.net.Proxy import java.net.Proxy
import java.net.SocketAddress
import java.nio.file.Paths
import java.security.PublicKey
import java.time.Duration import java.time.Duration
import java.util.concurrent.atomic.AtomicBoolean
import javax.swing.JOptionPane
import javax.swing.SwingUtilities
import kotlin.math.max import kotlin.math.max
object SshClients { object SshClients {
@@ -192,3 +205,73 @@ object SshClients {
return sshClient return sshClient
} }
} }
private class MyDialogServerKeyVerifier(private val owner: Window) : ServerKeyVerifier, ModifiedServerKeyAcceptor {
override fun verifyServerKey(
clientSession: ClientSession,
remoteAddress: SocketAddress,
serverKey: PublicKey
): Boolean {
val result = AtomicBoolean(false)
SwingUtilities.invokeAndWait {
result.set(
OptionPane.showConfirmDialog(
parentComponent = owner,
message = I18n.getString(
"termora.host.verify-server-key",
remoteAddress.toString().replace("/", StringUtils.EMPTY),
KeyUtils.getKeyType(serverKey),
KeyUtils.getFingerPrint(serverKey)
),
optionType = JOptionPane.OK_CANCEL_OPTION,
messageType = JOptionPane.WARNING_MESSAGE,
) == JOptionPane.OK_OPTION
)
}
return result.get()
}
override fun acceptModifiedServerKey(
clientSession: ClientSession?,
remoteAddress: SocketAddress?,
entry: KnownHostEntry?,
expected: PublicKey?,
actual: PublicKey?
): Boolean {
val result = AtomicBoolean(false)
SwingUtilities.invokeAndWait {
result.set(
OptionPane.showConfirmDialog(
parentComponent = owner,
message = I18n.getString(
"termora.host.modified-server-key",
remoteAddress.toString().replace("/", StringUtils.EMPTY),
KeyUtils.getKeyType(expected),
KeyUtils.getFingerPrint(expected),
KeyUtils.getKeyType(actual),
KeyUtils.getFingerPrint(actual),
),
optionType = JOptionPane.OK_CANCEL_OPTION,
messageType = JOptionPane.WARNING_MESSAGE,
) == JOptionPane.OK_OPTION
)
}
return result.get()
}
}
class DialogServerKeyVerifier(
owner: Window,
) : KnownHostsServerKeyVerifier(
MyDialogServerKeyVerifier(owner),
Paths.get(Application.getBaseDataDir().absolutePath, "known_hosts")
) {
init {
modifiedServerKeyAcceptor = delegateVerifier as ModifiedServerKeyAcceptor
}
}

View File

@@ -115,8 +115,9 @@ class SftpFileSystemPanel(
try { try {
val client = SshClients.openClient(host).apply { client = this } val client = SshClients.openClient(host).apply { client = this }
withContext(Dispatchers.Swing) { withContext(Dispatchers.Swing) {
client.userInteraction = val owner = SwingUtilities.getWindowAncestor(this@SftpFileSystemPanel)
TerminalUserInteraction(SwingUtilities.getWindowAncestor(this@SftpFileSystemPanel)) client.userInteraction = TerminalUserInteraction(owner)
client.serverKeyVerifier = DialogServerKeyVerifier(owner)
} }
val session = SshClients.openSession(host, client).apply { session = this } val session = SshClients.openSession(host, client).apply { session = this }
fileSystem = SftpClientFactory.instance().createSftpFileSystem(session) fileSystem = SftpClientFactory.instance().createSftpFileSystem(session)

View File

@@ -38,6 +38,9 @@ termora.doorman.mnemonic.title=Enter 12 mnemonic words
termora.doorman.mnemonic.incorrect=Incorrect mnemonic termora.doorman.mnemonic.incorrect=Incorrect mnemonic
# Hosts
termora.host.verify-server-key=Host [{0}] key has been changed!<br/><br/>{1} key fingerprint is {2}<br/><br/>Are you sure you want to continue connecting?
termora.host.modified-server-key=HOST [{0}] IDENTIFICATION HAS CHANGED!<br/><br/>Expected: {1} key fingerprint is {2}<br/><br/>Actual: {3} key fingerprint is {4}<br/><br/>Are you sure you want to continue connecting?
# Settings # Settings

View File

@@ -36,6 +36,11 @@ termora.doorman.mnemonic.title=输入 12 个助记词
termora.doorman.mnemonic.incorrect=助记词错误 termora.doorman.mnemonic.incorrect=助记词错误
# Hosts
termora.host.verify-server-key=主机 [{0}] 密钥已经改变!<br/><br/>{1} 的指纹 {2}<br/><br/>你确定要继续连接吗?
termora.host.modified-server-key=主机 [{0}] 身份已发生变化!<br/><br/>期待: {1} 的指纹 {2}<br/><br/>实际: {3} 的指纹 {4}<br/><br/>你确定要继续连接吗?
termora.setting=设置 termora.setting=设置
termora.settings.restart.title=重启 termora.settings.restart.title=重启
termora.settings.restart.message=设置修改将在重启后生效 termora.settings.restart.message=设置修改将在重启后生效

View File

@@ -35,6 +35,13 @@ termora.doorman.mnemonic-data-corrupted=無法從助記詞解密數據,資料
termora.doorman.mnemonic.title=輸入 12 個助記詞 termora.doorman.mnemonic.title=輸入 12 個助記詞
termora.doorman.mnemonic.incorrect=助記詞錯誤 termora.doorman.mnemonic.incorrect=助記詞錯誤
# Hosts
termora.host.verify-server-key=主機 [{0}] 金鑰已經改變!<br/><br/>{1} 的指紋 {2}<br/><br/>你確定要繼續連線嗎?
termora.host.modified-server-key=主機 [{0}] 身分已變更!<br/><br/>期待: {1} 的指紋 {2}<br/><br/>實際: {3} 的指紋 {4}<br/><br/>你確定要繼續連線嗎?
termora.setting=設定 termora.setting=設定
termora.settings.restart.title=重啟 termora.settings.restart.title=重啟
termora.settings.restart.message=設定修改將在重新啟動後生效 termora.settings.restart.message=設定修改將在重新啟動後生效