From a9f82cd7b84bdc66cd3a63560880ce80905f8097 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Fri, 13 Feb 2026 20:07:35 +0800 Subject: [PATCH] chore(formatting): add spotless plugin --- sshlib/api.txt | 4 +- sshlib/build.gradle.kts | 18 ++ .../org/connectbot/sshlib/AgentProvider.kt | 4 +- .../org/connectbot/sshlib/AuthHandler.kt | 2 +- .../org/connectbot/sshlib/HostKeyVerifier.kt | 8 +- .../sshlib/KeyboardInteractiveCallback.kt | 2 +- .../kotlin/org/connectbot/sshlib/SshClient.kt | 67 ++--- .../org/connectbot/sshlib/SshClientConfig.kt | 15 +- .../org/connectbot/sshlib/SshException.kt | 2 +- .../kotlin/org/connectbot/sshlib/SshKeys.kt | 12 +- .../org/connectbot/sshlib/SshSession.kt | 2 +- .../org/connectbot/sshlib/SshSigning.kt | 10 +- .../sshlib/blocking/BlockingSshClient.kt | 51 ++-- .../connectbot/sshlib/client/AgentChannel.kt | 2 +- .../sshlib/client/AgentProtocolHandler.kt | 24 +- .../connectbot/sshlib/client/AuthResult.kt | 2 +- .../connectbot/sshlib/client/DataForwarder.kt | 15 +- .../sshlib/client/DynamicPortForwarder.kt | 34 ++- .../sshlib/client/ForwardingChannel.kt | 2 +- .../sshlib/client/LocalPortForwarder.kt | 35 ++- .../sshlib/client/RemotePortForwarder.kt | 26 +- .../sshlib/client/SessionChannel.kt | 17 +- .../connectbot/sshlib/client/Socks5Handler.kt | 16 +- .../connectbot/sshlib/client/SshConnection.kt | 258 ++++++++++++++---- .../sshlib/client/StreamForwarder.kt | 12 +- .../connectbot/sshlib/crypto/AesCbcCipher.kt | 10 +- .../connectbot/sshlib/crypto/AesCtrCipher.kt | 10 +- .../connectbot/sshlib/crypto/AesGcmCipher.kt | 2 +- .../connectbot/sshlib/crypto/Algorithms.kt | 169 ++++++++---- .../sshlib/crypto/Curve25519KeyExchange.kt | 2 +- .../org/connectbot/sshlib/crypto/DhGroups.kt | 160 +++++------ .../connectbot/sshlib/crypto/DiffieHellman.kt | 3 +- .../crypto/DiffieHellmanGroupExchange.kt | 30 +- .../sshlib/crypto/EcdhKeyExchange.kt | 12 +- .../sshlib/crypto/EcdsaSignatureAlgorithm.kt | 14 +- .../crypto/Ed25519SignatureAlgorithm.kt | 2 +- .../sshlib/crypto/Ed448SignatureAlgorithm.kt | 2 +- .../sshlib/crypto/JavaMlKemProvider.kt | 10 +- .../connectbot/sshlib/crypto/KexAlgorithm.kt | 16 +- .../connectbot/sshlib/crypto/KeyDecryption.kt | 6 +- .../connectbot/sshlib/crypto/KeyDerivation.kt | 22 +- .../connectbot/sshlib/crypto/KeyEncryption.kt | 8 +- .../org/connectbot/sshlib/crypto/KeyTypes.kt | 37 +-- .../sshlib/crypto/MlKemHybridKeyExchange.kt | 2 +- .../sshlib/crypto/OpenSshKeyReader.kt | 13 +- .../sshlib/crypto/OpenSshKeyWriter.kt | 24 +- .../connectbot/sshlib/crypto/PacketAead.kt | 6 +- .../connectbot/sshlib/crypto/PemKeyReader.kt | 36 ++- .../sshlib/crypto/PlatformX25519Provider.kt | 20 +- .../org/connectbot/sshlib/crypto/Poly1305.kt | 49 +++- .../sshlib/crypto/PrivateKeyReader.kt | 22 +- .../sshlib/crypto/RsaSignatureAlgorithm.kt | 2 +- .../connectbot/sshlib/crypto/SshPrivateKey.kt | 2 +- .../sshlib/crypto/SshPublicKeyEncoder.kt | 50 ++-- .../sshlib/crypto/TinkX25519Provider.kt | 6 +- .../sshlib/crypto/TripleDesCbcCipher.kt | 10 +- .../sshlib/crypto/X25519ProviderFactory.kt | 4 +- .../crypto/ed25519/Ed25519KeyFactory.kt | 4 +- .../crypto/ed25519/Ed25519PrivateKey.kt | 4 +- .../sshlib/crypto/ed25519/Ed25519Provider.kt | 10 +- .../connectbot/sshlib/protocol/SshBufUtils.kt | 20 +- .../sshlib/protocol/SshClientStateMachine.kt | 19 +- .../transport/ForwardingChannelTransport.kt | 2 +- .../sshlib/transport/KtorTcpTransport.kt | 27 +- .../connectbot/sshlib/transport/PacketIO.kt | 18 +- .../sshlib/transport/TransportDependencies.kt | 16 +- .../sshlib/transport/TransportFactory.kt | 8 +- .../connectbot/sshlib/AgentProtocolTest.kt | 42 +-- .../connectbot/sshlib/AgentProviderTest.kt | 35 ++- .../org/connectbot/sshlib/SshKeysTest.kt | 6 +- .../org/connectbot/sshlib/SshSigningTest.kt | 6 +- .../sshlib/client/ForwardingChannelTest.kt | 8 +- .../client/PortForwardingIntegrationTest.kt | 29 +- .../sshlib/client/SessionChannelTest.kt | 12 +- .../sshlib/client/Socks5HandlerTest.kt | 20 +- .../sshlib/client/SshClientIntegrationTest.kt | 55 ++-- .../sshlib/client/StreamForwarderTest.kt | 28 +- .../sshlib/crypto/AesGcmCipherTest.kt | 3 +- .../sshlib/crypto/AlgorithmsTest.kt | 7 +- .../crypto/ChaCha20Poly1305CipherTest.kt | 7 +- .../crypto/Curve25519KeyExchangeTest.kt | 4 +- .../org/connectbot/sshlib/crypto/DerTest.kt | 6 +- .../crypto/DiffieHellmanGroupExchangeTest.kt | 22 +- .../sshlib/crypto/DiffieHellmanTest.kt | 33 ++- .../sshlib/crypto/EcdhKeyExchangeTest.kt | 33 ++- .../crypto/MlKemHybridKeyExchangeTest.kt | 15 +- .../sshlib/crypto/OpenSshKeyWriterTest.kt | 6 +- .../sshlib/crypto/PemKeyWriterTest.kt | 8 +- .../sshlib/crypto/PrivateKeyReaderTest.kt | 6 +- .../sshlib/crypto/SignatureGenerationTest.kt | 6 +- .../connectbot/sshlib/protocol/CaptureTest.kt | 5 + .../sshlib/transport/ByteArrayTransport.kt | 2 +- .../ForwardingChannelTransportTest.kt | 7 +- .../sshlib/transport/KtorTcpTransportTest.kt | 20 +- 94 files changed, 1191 insertions(+), 769 deletions(-) diff --git a/sshlib/api.txt b/sshlib/api.txt index ee8d510..4234f4a 100644 --- a/sshlib/api.txt +++ b/sshlib/api.txt @@ -378,8 +378,8 @@ package org.connectbot.sshlib.transport { public enum IpVersion { enum_constant public static final org.connectbot.sshlib.transport.IpVersion AUTO; - enum_constant public static final org.connectbot.sshlib.transport.IpVersion IPv4_ONLY; - enum_constant public static final org.connectbot.sshlib.transport.IpVersion IPv6_ONLY; + enum_constant public static final org.connectbot.sshlib.transport.IpVersion IPV4_ONLY; + enum_constant public static final org.connectbot.sshlib.transport.IpVersion IPV6_ONLY; } public final class KtorTcpTransport implements org.connectbot.sshlib.transport.Transport { diff --git a/sshlib/build.gradle.kts b/sshlib/build.gradle.kts index 269b4aa..c30a9d7 100644 --- a/sshlib/build.gradle.kts +++ b/sshlib/build.gradle.kts @@ -92,6 +92,24 @@ spotless { ktlint() } + kotlin { + ktlint("1.8.0") + .editorConfigOverride( + mapOf( + "ij_kotlin_imports_layout" to "*,java.**,javax.**,kotlin.**,^", + "ij_kotlin_allow_trailing_comma" to "true", + "ktlint_code_style" to "android_studio", + "ktlint_function_naming_ignore_when_annotated_with" to "Composable", + "ktlint_standard_property-naming" to "disabled", + "ktlint_standard_backing-property-naming" to "disabled", + "ktlint_standard_filename" to "disabled", + "ktlint_standard_discouraged-comment-location" to "disabled", + "ktlint_standard_max-line-length" to "disabled", + "ktlint_standard_kdoc" to "disabled", + ), + ) + } + format("xml") { target( fileTree(".") { diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/AgentProvider.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/AgentProvider.kt index 1d50caa..1176f8c 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/AgentProvider.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/AgentProvider.kt @@ -53,7 +53,7 @@ interface AgentProvider { */ data class AgentIdentity( val publicKeyBlob: ByteArray, - val comment: String + val comment: String, ) { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -94,7 +94,7 @@ data class AgentSigningContext( val serverHostKey: ByteArray, /** Whether session-bind extension was used */ - val isBound: Boolean + val isBound: Boolean, ) { override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/AuthHandler.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/AuthHandler.kt index 2c35723..fbb9437 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/AuthHandler.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/AuthHandler.kt @@ -51,7 +51,7 @@ interface AuthHandler { suspend fun onKeyboardInteractivePrompt( name: String, instruction: String, - prompts: List + prompts: List, ): List? /** diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/HostKeyVerifier.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/HostKeyVerifier.kt index 40ecf1b..049e9d0 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/HostKeyVerifier.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/HostKeyVerifier.kt @@ -46,7 +46,7 @@ interface HostKeyVerifier { */ data class PublicKey( val type: String, - val encoded: ByteArray + val encoded: ByteArray, ) { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -77,7 +77,7 @@ data class PublicKey( class KnownHostsVerifier( private val file: File, private val hostname: String, - private val port: Int = 22 + private val port: Int = 22, ) : HostKeyVerifier { private val hostAlias: String = if (port == 22) hostname else "[$hostname]:$port" @@ -106,7 +106,7 @@ class KnownHostsVerifier( // Simple wildcard matching // Note: Java Regex or simple manual matching. // Keeping it simple: exact match or basic wildcard - if (pattern.contains("*" ) || pattern.contains("?")) { + if (pattern.contains("*") || pattern.contains("?")) { val regex = pattern .replace(".", "\\.") .replace("*", ".*") @@ -137,6 +137,6 @@ class KnownHostsVerifier( private data class Entry( val hosts: List, - val key: PublicKey + val key: PublicKey, ) } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/KeyboardInteractiveCallback.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/KeyboardInteractiveCallback.kt index 858d85c..58171dd 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/KeyboardInteractiveCallback.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/KeyboardInteractiveCallback.kt @@ -36,7 +36,7 @@ interface KeyboardInteractiveCallback { name: String, instruction: String, prompts: List, - respond: suspend (responses: List) -> Unit + respond: suspend (responses: List) -> Unit, ) data class Prompt(val text: String, val echo: Boolean) diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClient.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClient.kt index e0802cc..e28b117 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClient.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClient.kt @@ -16,6 +16,8 @@ package org.connectbot.sshlib +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.ByteWriteChannel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -24,14 +26,12 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch -import io.ktor.utils.io.* import org.connectbot.sshlib.client.DynamicPortForwarder import org.connectbot.sshlib.client.LocalPortForwarder import org.connectbot.sshlib.client.RemotePortForwarder import org.connectbot.sshlib.client.SshConnection import org.connectbot.sshlib.crypto.PrivateKeyReader import org.connectbot.sshlib.transport.ForwardingChannelTransport -import org.connectbot.sshlib.transport.KtorTcpTransportFactory import org.connectbot.sshlib.transport.Transport import org.connectbot.sshlib.transport.TransportFactory import org.slf4j.LoggerFactory @@ -70,7 +70,7 @@ import java.net.InetSocketAddress * For blocking Java compatibility, use [org.connectbot.sshlib.blocking.BlockingSshClient]. */ class SshClient private constructor( - private val config: SshClientConfig + private val config: SshClientConfig, ) { companion object { private val logger = LoggerFactory.getLogger(SshClient::class.java) @@ -85,7 +85,7 @@ class SshClient private constructor( operator fun invoke( host: String, port: Int = 22, - clientVersion: String = "SSH-2.0-CBSSH_1.0" + clientVersion: String = "SSH-2.0-CBSSH_1.0", ): SshClient { val config = SshClientConfig { this.host = host @@ -98,9 +98,7 @@ class SshClient private constructor( /** * Create an SshClient from a configuration. */ - operator fun invoke(config: SshClientConfig): SshClient { - return SshClient(config) - } + operator fun invoke(config: SshClientConfig): SshClient = SshClient(config) /** * Create an SshClient with a custom transport factory. @@ -110,7 +108,7 @@ class SshClient private constructor( */ operator fun invoke( transportFactory: TransportFactory, - clientVersion: String = "SSH-2.0-CBSSH_1.0" + clientVersion: String = "SSH-2.0-CBSSH_1.0", ): SshClient { val config = SshClientConfig { this.transportFactory = transportFactory @@ -224,7 +222,7 @@ class SshClient private constructor( */ suspend fun authenticateKeyboardInteractive( username: String, - callback: KeyboardInteractiveCallback + callback: KeyboardInteractiveCallback, ): Boolean { val conn = connection if (conn == null) { @@ -261,10 +259,8 @@ class SshClient private constructor( suspend fun authenticatePublicKey( username: String, privateKeyData: ByteArray, - passphrase: String? = null - ): Boolean { - return authenticatePublicKey(username, String(privateKeyData, Charsets.UTF_8), passphrase) - } + passphrase: String? = null, + ): Boolean = authenticatePublicKey(username, String(privateKeyData, Charsets.UTF_8), passphrase) /** * Authenticate using public key authentication (RFC 4252 §7). @@ -277,7 +273,7 @@ class SshClient private constructor( suspend fun authenticatePublicKey( username: String, privateKeyData: String, - passphrase: String? = null + passphrase: String? = null, ): Boolean { val conn = connection if (conn == null) { @@ -358,9 +354,7 @@ class SshClient private constructor( * @param privateKeyData Private key file contents * @return true if the key is encrypted */ - fun isPrivateKeyEncrypted(privateKeyData: ByteArray): Boolean { - return isPrivateKeyEncrypted(String(privateKeyData, Charsets.UTF_8)) - } + fun isPrivateKeyEncrypted(privateKeyData: ByteArray): Boolean = isPrivateKeyEncrypted(String(privateKeyData, Charsets.UTF_8)) /** * Check if the provided private key data is encrypted and requires a passphrase. @@ -368,13 +362,11 @@ class SshClient private constructor( * @param privateKeyData Private key file contents as a string * @return true if the key is encrypted */ - fun isPrivateKeyEncrypted(privateKeyData: String): Boolean { - return try { - PrivateKeyReader.isEncrypted(privateKeyData) - } catch (e: Exception) { - logger.error("Failed to check if key is encrypted", e) - false - } + fun isPrivateKeyEncrypted(privateKeyData: String): Boolean = try { + PrivateKeyReader.isEncrypted(privateKeyData) + } catch (e: Exception) { + logger.error("Failed to check if key is encrypted", e) + false } /** @@ -418,7 +410,7 @@ class SshClient private constructor( suspend fun localPortForward( bindAddress: InetSocketAddress, remoteHost: String, - remotePort: Int + remotePort: Int, ): PortForwarder? { val conn = connection if (conn == null || !authenticated) { @@ -445,7 +437,7 @@ class SshClient private constructor( suspend fun localPortForward( bindPort: Int, remoteHost: String, - remotePort: Int + remotePort: Int, ): PortForwarder? = localPortForward(InetSocketAddress("127.0.0.1", bindPort), remoteHost, remotePort) /** @@ -464,7 +456,7 @@ class SshClient private constructor( remoteBindAddress: String, remoteBindPort: Int, localHost: String, - localPort: Int + localPort: Int, ): PortForwarder? { val conn = connection if (conn == null || !authenticated) { @@ -492,7 +484,7 @@ class SshClient private constructor( */ suspend fun dynamicPortForward( bindAddress: InetSocketAddress, - authenticator: Socks5Authenticator? = null + authenticator: Socks5Authenticator? = null, ): PortForwarder? { val conn = connection if (conn == null || !authenticated) { @@ -517,7 +509,7 @@ class SshClient private constructor( */ suspend fun dynamicPortForward( bindPort: Int, - authenticator: Socks5Authenticator? = null + authenticator: Socks5Authenticator? = null, ): PortForwarder? = dynamicPortForward(InetSocketAddress("127.0.0.1", bindPort), authenticator) /** @@ -540,7 +532,7 @@ class SshClient private constructor( remoteHost: String, remotePort: Int, originAddr: String = "127.0.0.1", - originPort: Int = 0 + originPort: Int = 0, ): StreamForwarder? { val conn = connection if (conn == null || !authenticated) { @@ -550,7 +542,13 @@ class SshClient private constructor( return try { org.connectbot.sshlib.client.StreamForwarder.create( - conn, readChannel, writeChannel, remoteHost, remotePort, originAddr, originPort + conn, + readChannel, + writeChannel, + remoteHost, + remotePort, + originAddr, + originPort ) } catch (e: Exception) { logger.error("Failed to start stream forwarding", e) @@ -589,7 +587,7 @@ class SshClient private constructor( remoteHost: String, remotePort: Int, originAddr: String = "127.0.0.1", - originPort: Int = 0 + originPort: Int = 0, ): TransportFactory? { val conn = connection if (conn == null || !authenticated) { @@ -599,7 +597,10 @@ class SshClient private constructor( return TransportFactory { val channel = conn.openDirectTcpipChannel( - remoteHost, remotePort, originAddr, originPort + remoteHost, + remotePort, + originAddr, + originPort ) ?: throw SshException("Failed to open direct-tcpip channel to $remoteHost:$remotePort") ForwardingChannelTransport(channel) } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClientConfig.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClientConfig.kt index 103718a..0a594d6 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClientConfig.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClientConfig.kt @@ -52,7 +52,7 @@ class SshClientConfig private constructor( val hostKeyAlgorithms: String, val encryptionAlgorithms: String, val macAlgorithms: String, - internal val compressionAlgorithms: String + internal val compressionAlgorithms: String, ) { class Builder { /** @@ -116,16 +116,19 @@ class SshClientConfig private constructor( } return SshClientConfig( - factory, clientVersion, verifier, - kexAlgorithms, hostKeyAlgorithms, encryptionAlgorithms, macAlgorithms, + factory, + clientVersion, + verifier, + kexAlgorithms, + hostKeyAlgorithms, + encryptionAlgorithms, + macAlgorithms, compressionAlgorithms ) } } companion object { - operator fun invoke(block: Builder.() -> Unit): SshClientConfig { - return Builder().apply(block).build() - } + operator fun invoke(block: Builder.() -> Unit): SshClientConfig = Builder().apply(block).build() } } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshException.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshException.kt index 74cab56..184b77c 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshException.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshException.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + package org.connectbot.sshlib open class SshException(message: String, cause: Throwable? = null) : Exception(message, cause) diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshKeys.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshKeys.kt index b757788..d678550 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshKeys.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshKeys.kt @@ -45,9 +45,7 @@ object SshKeys { * @return JCA [KeyPair] containing both public and private keys * @throws SshException if the key format is unrecognized or decryption fails */ - fun decodePemPrivateKey(pem: String, password: String? = null): KeyPair { - return PrivateKeyReader.read(pem, password).jcaKeyPair - } + fun decodePemPrivateKey(pem: String, password: String? = null): KeyPair = PrivateKeyReader.read(pem, password).jcaKeyPair /** * Encode a [KeyPair] to PEM format. @@ -61,9 +59,7 @@ object SshKeys { * @return PEM-encoded private key string * @throws SshException if the key type is unsupported */ - fun encodePemPrivateKey(keyPair: KeyPair, password: String? = null): String { - return PemKeyWriter.write(keyPair, password) - } + fun encodePemPrivateKey(keyPair: KeyPair, password: String? = null): String = PemKeyWriter.write(keyPair, password) /** * Encode a [KeyPair] to OpenSSH format (`BEGIN OPENSSH PRIVATE KEY`). @@ -73,9 +69,7 @@ object SshKeys { * @return OpenSSH-formatted private key string * @throws SshException if the key type is unsupported */ - fun encodeOpenSshPrivateKey(keyPair: KeyPair, password: String? = null): String { - return OpenSshKeyWriter.write(keyPair, password) - } + fun encodeOpenSshPrivateKey(keyPair: KeyPair, password: String? = null): String = OpenSshKeyWriter.write(keyPair, password) /** * Ensure Ed25519 JCA support is available. diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshSession.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshSession.kt index 2867e55..ad1b40f 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshSession.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshSession.kt @@ -38,7 +38,7 @@ interface SshSession : AutoCloseable { heightRows: Int = 24, widthPixels: Int = 0, heightPixels: Int = 0, - terminalModes: ByteArray = byteArrayOf(0) + terminalModes: ByteArray = byteArrayOf(0), ): Boolean suspend fun resizeTerminal( diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshSigning.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshSigning.kt index 9de2429..d4c2878 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshSigning.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshSigning.kt @@ -44,7 +44,7 @@ object SshSigning { algorithmName: String, privateKeyData: String, passphrase: String?, - dataToSign: ByteArray + dataToSign: ByteArray, ): ByteArray { val privateKey = PrivateKeyReader.read(privateKeyData, passphrase) val sigEntry = SignatureEntry.fromSshName(algorithmName) @@ -65,7 +65,7 @@ object SshSigning { algorithmName: String, privateKeyData: ByteArray, passphrase: String?, - dataToSign: ByteArray + dataToSign: ByteArray, ): ByteArray = sign(algorithmName, String(privateKeyData, Charsets.UTF_8), passphrase, dataToSign) /** @@ -79,7 +79,7 @@ object SshSigning { fun getPublicKey( algorithmName: String, privateKeyData: String, - passphrase: String? + passphrase: String?, ): AuthPublicKey { val privateKey = PrivateKeyReader.read(privateKeyData, passphrase) val blob = SshPublicKeyEncoder.encode(privateKey.jcaKeyPair, privateKey.keyType) @@ -97,7 +97,7 @@ object SshSigning { fun getPublicKey( algorithmName: String, privateKeyData: ByteArray, - passphrase: String? + passphrase: String?, ): AuthPublicKey = getPublicKey(algorithmName, String(privateKeyData, Charsets.UTF_8), passphrase) /** @@ -129,7 +129,7 @@ object SshSigning { fun signWithKeyPair( algorithmName: String, keyPair: KeyPair, - dataToSign: ByteArray + dataToSign: ByteArray, ): ByteArray { val sigEntry = SignatureEntry.fromSshName(algorithmName) ?: throw SshException("Unknown signature algorithm: $algorithmName") diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/blocking/BlockingSshClient.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/blocking/BlockingSshClient.kt index 13e0a41..92a09f5 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/blocking/BlockingSshClient.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/blocking/BlockingSshClient.kt @@ -16,7 +16,8 @@ package org.connectbot.sshlib.blocking -import io.ktor.utils.io.* +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.ByteWriteChannel import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.runBlocking import org.connectbot.sshlib.AgentProvider @@ -25,10 +26,10 @@ import org.connectbot.sshlib.HostKeyVerifier import org.connectbot.sshlib.KeyboardInteractiveCallback import org.connectbot.sshlib.PortForwarder import org.connectbot.sshlib.Socks5Authenticator -import org.connectbot.sshlib.StreamForwarder import org.connectbot.sshlib.SshClient import org.connectbot.sshlib.SshClientConfig import org.connectbot.sshlib.SshSession +import org.connectbot.sshlib.StreamForwarder import org.connectbot.sshlib.transport.TransportFactory import java.net.InetSocketAddress @@ -58,7 +59,7 @@ import java.net.InetSocketAddress * ``` */ class BlockingSshClient private constructor( - private val client: SshClient + private val client: SshClient, ) { /** * Create a blocking SSH client for TCP connection. @@ -68,13 +69,15 @@ class BlockingSshClient private constructor( host: String, port: Int = 22, hostKeyVerifier: HostKeyVerifier, - clientVersion: String = "SSH-2.0-CBSSH_1.0" - ) : this(SshClientConfig { - this.host = host - this.port = port - this.hostKeyVerifier = hostKeyVerifier - this.clientVersion = clientVersion - }) + clientVersion: String = "SSH-2.0-CBSSH_1.0", + ) : this( + SshClientConfig { + this.host = host + this.port = port + this.hostKeyVerifier = hostKeyVerifier + this.clientVersion = clientVersion + } + ) /** * Create a blocking SSH client from configuration. @@ -86,7 +89,7 @@ class BlockingSshClient private constructor( */ constructor( transportFactory: TransportFactory, - clientVersion: String = "SSH-2.0-CBSSH_1.0" + clientVersion: String = "SSH-2.0-CBSSH_1.0", ) : this(SshClient(transportFactory, clientVersion)) /** @@ -103,8 +106,7 @@ class BlockingSshClient private constructor( * @param password SSH password * @return true if authentication succeeded */ - fun authenticatePassword(username: String, password: String): Boolean = - runBlocking { client.authenticatePassword(username, password) } + fun authenticatePassword(username: String, password: String): Boolean = runBlocking { client.authenticatePassword(username, password) } /** * Authenticate using the strategy-based [AuthHandler] flow. @@ -113,8 +115,7 @@ class BlockingSshClient private constructor( * @param handler Callback handler providing authentication materials * @return true if authentication succeeded */ - fun authenticate(username: String, handler: AuthHandler): Boolean = - runBlocking { client.authenticate(username, handler) } + fun authenticate(username: String, handler: AuthHandler): Boolean = runBlocking { client.authenticate(username, handler) } /** * Authenticate using keyboard-interactive authentication (RFC 4256). @@ -125,7 +126,7 @@ class BlockingSshClient private constructor( */ fun authenticateKeyboardInteractive( username: String, - callback: KeyboardInteractiveCallback + callback: KeyboardInteractiveCallback, ): Boolean = runBlocking { client.authenticateKeyboardInteractive(username, callback) } /** @@ -140,7 +141,7 @@ class BlockingSshClient private constructor( fun authenticatePublicKey( username: String, privateKeyData: ByteArray, - passphrase: String? = null + passphrase: String? = null, ): Boolean = runBlocking { client.authenticatePublicKey(username, privateKeyData, passphrase) } /** @@ -155,7 +156,7 @@ class BlockingSshClient private constructor( fun authenticatePublicKey( username: String, privateKeyData: String, - passphrase: String? = null + passphrase: String? = null, ): Boolean = runBlocking { client.authenticatePublicKey(username, privateKeyData, passphrase) } /** @@ -197,7 +198,7 @@ class BlockingSshClient private constructor( fun localPortForward( bindAddress: InetSocketAddress, remoteHost: String, - remotePort: Int + remotePort: Int, ): PortForwarder? = runBlocking { client.localPortForward(bindAddress, remoteHost, remotePort) } /** @@ -206,7 +207,7 @@ class BlockingSshClient private constructor( fun localPortForward( bindPort: Int, remoteHost: String, - remotePort: Int + remotePort: Int, ): PortForwarder? = runBlocking { client.localPortForward(bindPort, remoteHost, remotePort) } /** @@ -216,7 +217,7 @@ class BlockingSshClient private constructor( remoteBindAddress: String, remoteBindPort: Int, localHost: String, - localPort: Int + localPort: Int, ): PortForwarder? = runBlocking { client.remotePortForward(remoteBindAddress, remoteBindPort, localHost, localPort) } /** @@ -225,7 +226,7 @@ class BlockingSshClient private constructor( @JvmOverloads fun dynamicPortForward( bindAddress: InetSocketAddress, - authenticator: Socks5Authenticator? = null + authenticator: Socks5Authenticator? = null, ): PortForwarder? = runBlocking { client.dynamicPortForward(bindAddress, authenticator) } /** @@ -234,7 +235,7 @@ class BlockingSshClient private constructor( @JvmOverloads fun dynamicPortForward( bindPort: Int, - authenticator: Socks5Authenticator? = null + authenticator: Socks5Authenticator? = null, ): PortForwarder? = runBlocking { client.dynamicPortForward(bindPort, authenticator) } /** @@ -247,7 +248,7 @@ class BlockingSshClient private constructor( remoteHost: String, remotePort: Int, originAddr: String = "127.0.0.1", - originPort: Int = 0 + originPort: Int = 0, ): StreamForwarder? = runBlocking { client.forwardStream(readChannel, writeChannel, remoteHost, remotePort, originAddr, originPort) } @@ -260,7 +261,7 @@ class BlockingSshClient private constructor( remoteHost: String, remotePort: Int, originAddr: String = "127.0.0.1", - originPort: Int = 0 + originPort: Int = 0, ): TransportFactory? = client.openDirectTcpipTransport(remoteHost, remotePort, originAddr, originPort) /** diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/AgentChannel.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/AgentChannel.kt index 8e3c6ce..d128d1a 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/AgentChannel.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/AgentChannel.kt @@ -25,7 +25,7 @@ internal class AgentChannel( private val localChannelNumber: Int, private var remoteChannelNumber: Int, private val maxPacketSize: Int, - private var remoteWindowSize: Long + private var remoteWindowSize: Long, ) { companion object { private val logger = LoggerFactory.getLogger(AgentChannel::class.java) diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/AgentProtocolHandler.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/AgentProtocolHandler.kt index ce85904..51d4362 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/AgentProtocolHandler.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/AgentProtocolHandler.kt @@ -20,12 +20,19 @@ import io.kaitai.struct.ByteBufferKaitaiStream import io.kaitai.struct.KaitaiStruct import org.connectbot.sshlib.AgentProvider import org.connectbot.sshlib.AgentSigningContext -import org.connectbot.sshlib.protocol.* +import org.connectbot.sshlib.protocol.SshAgentIdentitiesAnswer +import org.connectbot.sshlib.protocol.SshAgentMessage +import org.connectbot.sshlib.protocol.SshAgentSignResponse +import org.connectbot.sshlib.protocol.SshAgentcExtension +import org.connectbot.sshlib.protocol.SshAgentcSessionBind +import org.connectbot.sshlib.protocol.SshAgentcSignRequest +import org.connectbot.sshlib.protocol.createByteString +import org.connectbot.sshlib.protocol.toByteArray import org.slf4j.LoggerFactory internal class AgentProtocolHandler( private val provider: AgentProvider, - private val sessionInfo: AgentSessionInfo + private val sessionInfo: AgentSessionInfo, ) { companion object { private val logger = LoggerFactory.getLogger(AgentProtocolHandler::class.java) @@ -54,8 +61,11 @@ internal class AgentProtocolHandler( return when (messageType) { SSH_AGENTC_REQUEST_IDENTITIES -> handleRequestIdentities() + SSH_AGENTC_SIGN_REQUEST -> handleSignRequest(message) + SSH_AGENTC_EXTENSION -> handleExtension(message) + else -> { logger.warn("Unknown agent message type: $messageType") createFailureResponse() @@ -181,18 +191,14 @@ internal class AgentProtocolHandler( return buffer } - private fun createFailureResponse(): ByteArray { - return buildAgentMessage(SSH_AGENT_FAILURE, ByteArray(0)) - } + private fun createFailureResponse(): ByteArray = buildAgentMessage(SSH_AGENT_FAILURE, ByteArray(0)) - private fun createSuccessResponse(): ByteArray { - return buildAgentMessage(SSH_AGENT_SUCCESS, ByteArray(0)) - } + private fun createSuccessResponse(): ByteArray = buildAgentMessage(SSH_AGENT_SUCCESS, ByteArray(0)) } internal data class AgentSessionInfo( val sessionId: ByteArray, - val serverHostKey: ByteArray + val serverHostKey: ByteArray, ) { override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/AuthResult.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/AuthResult.kt index 2ff4534..7be8085 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/AuthResult.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/AuthResult.kt @@ -25,6 +25,6 @@ internal sealed class AuthResult { data class InfoRequest( val name: String, val instruction: String, - val prompts: List + val prompts: List, ) : AuthResult() } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/DataForwarder.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/DataForwarder.kt index b3e5b5f..3bf7478 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/DataForwarder.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/DataForwarder.kt @@ -16,8 +16,17 @@ package org.connectbot.sshlib.client -import io.ktor.utils.io.* -import kotlinx.coroutines.* +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.ByteWriteChannel +import io.ktor.utils.io.cancel +import io.ktor.utils.io.readAvailable +import io.ktor.utils.io.writeFully +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import org.slf4j.LoggerFactory import kotlin.coroutines.cancellation.CancellationException @@ -31,7 +40,7 @@ internal class DataForwarder( private val scope: CoroutineScope, private val sshChannel: ForwardingChannel, private val tcpRead: ByteReadChannel, - private val tcpWrite: ByteWriteChannel + private val tcpWrite: ByteWriteChannel, ) { companion object { private val logger = LoggerFactory.getLogger(DataForwarder::class.java) diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/DynamicPortForwarder.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/DynamicPortForwarder.kt index 9382d4a..94fdf96 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/DynamicPortForwarder.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/DynamicPortForwarder.kt @@ -16,10 +16,19 @@ package org.connectbot.sshlib.client -import io.ktor.network.selector.* -import io.ktor.network.sockets.* -import io.ktor.utils.io.* -import kotlinx.coroutines.* +import io.ktor.network.selector.SelectorManager +import io.ktor.network.sockets.ServerSocket +import io.ktor.network.sockets.Socket +import io.ktor.network.sockets.aSocket +import io.ktor.network.sockets.openReadChannel +import io.ktor.network.sockets.openWriteChannel +import io.ktor.network.sockets.toJavaAddress +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import org.connectbot.sshlib.PortForwarder import org.connectbot.sshlib.Socks5Authenticator import org.slf4j.LoggerFactory @@ -31,7 +40,7 @@ internal class DynamicPortForwarder( private val socks5Handler: Socks5Handler, private val serverSocket: ServerSocket, override val boundHost: String, - override val boundPort: Int + override val boundPort: Int, ) : PortForwarder { companion object { private val logger = LoggerFactory.getLogger(DynamicPortForwarder::class.java) @@ -40,7 +49,7 @@ internal class DynamicPortForwarder( scope: CoroutineScope, connection: SshConnection, bindAddress: InetSocketAddress, - authenticator: Socks5Authenticator? = null + authenticator: Socks5Authenticator? = null, ): DynamicPortForwarder { val selectorManager = SelectorManager(Dispatchers.IO) val serverSocket = aSocket(selectorManager).tcp().bind(bindAddress.hostString, bindAddress.port) @@ -48,8 +57,12 @@ internal class DynamicPortForwarder( val handler = Socks5Handler(authenticator) val forwarder = DynamicPortForwarder( - scope, connection, handler, serverSocket, - actualAddress.hostString, actualAddress.port + scope, + connection, + handler, + serverSocket, + actualAddress.hostString, + actualAddress.port ) forwarder.startAcceptLoop() return forwarder @@ -102,7 +115,10 @@ internal class DynamicPortForwarder( logger.debug("SOCKS5 CONNECT to ${request.host}:${request.port}") val sshChannel = connection.openDirectTcpipChannel( - request.host, request.port, originAddr, originPort + request.host, + request.port, + originAddr, + originPort ) if (sshChannel == null) { diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/ForwardingChannel.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/ForwardingChannel.kt index a779691..06ec200 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/ForwardingChannel.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/ForwardingChannel.kt @@ -27,7 +27,7 @@ internal class ForwardingChannel( var remoteChannelNumber: Int, private val maxPacketSize: Int, private var remoteWindowSize: Long, - private val initialWindowSize: Int = 256 * 1024 + private val initialWindowSize: Int = 256 * 1024, ) { companion object { private val logger = LoggerFactory.getLogger(ForwardingChannel::class.java) diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/LocalPortForwarder.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/LocalPortForwarder.kt index f0c0e4b..55f98ee 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/LocalPortForwarder.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/LocalPortForwarder.kt @@ -16,10 +16,19 @@ package org.connectbot.sshlib.client -import io.ktor.network.selector.* -import io.ktor.network.sockets.* -import io.ktor.utils.io.* -import kotlinx.coroutines.* +import io.ktor.network.selector.SelectorManager +import io.ktor.network.sockets.ServerSocket +import io.ktor.network.sockets.Socket +import io.ktor.network.sockets.aSocket +import io.ktor.network.sockets.openReadChannel +import io.ktor.network.sockets.openWriteChannel +import io.ktor.network.sockets.toJavaAddress +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import org.connectbot.sshlib.PortForwarder import org.slf4j.LoggerFactory import java.net.InetSocketAddress @@ -31,7 +40,7 @@ internal class LocalPortForwarder( private val remotePort: Int, private val serverSocket: ServerSocket, override val boundHost: String, - override val boundPort: Int + override val boundPort: Int, ) : PortForwarder { companion object { private val logger = LoggerFactory.getLogger(LocalPortForwarder::class.java) @@ -41,15 +50,20 @@ internal class LocalPortForwarder( connection: SshConnection, bindAddress: InetSocketAddress, remoteHost: String, - remotePort: Int + remotePort: Int, ): LocalPortForwarder { val selectorManager = SelectorManager(Dispatchers.IO) val serverSocket = aSocket(selectorManager).tcp().bind(bindAddress.hostString, bindAddress.port) val actualAddress = serverSocket.localAddress.toJavaAddress() as java.net.InetSocketAddress val forwarder = LocalPortForwarder( - scope, connection, remoteHost, remotePort, serverSocket, - actualAddress.hostString, actualAddress.port + scope, + connection, + remoteHost, + remotePort, + serverSocket, + actualAddress.hostString, + actualAddress.port ) forwarder.startAcceptLoop() return forwarder @@ -85,7 +99,10 @@ internal class LocalPortForwarder( logger.debug("Accepted connection from $originAddr:$originPort, forwarding to $remoteHost:$remotePort") val sshChannel = connection.openDirectTcpipChannel( - remoteHost, remotePort, originAddr, originPort + remoteHost, + remotePort, + originAddr, + originPort ) if (sshChannel == null) { diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/RemotePortForwarder.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/RemotePortForwarder.kt index 8f1809e..317a62a 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/RemotePortForwarder.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/RemotePortForwarder.kt @@ -16,10 +16,12 @@ package org.connectbot.sshlib.client -import io.ktor.network.selector.* -import io.ktor.network.sockets.* -import io.ktor.utils.io.* -import kotlinx.coroutines.* +import io.ktor.network.selector.SelectorManager +import io.ktor.network.sockets.aSocket +import io.ktor.network.sockets.openReadChannel +import io.ktor.network.sockets.openWriteChannel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import org.connectbot.sshlib.PortForwarder import org.slf4j.LoggerFactory @@ -31,7 +33,7 @@ internal class RemotePortForwarder( private val remoteBindAddress: String, private val remoteBindPort: Int, override val boundHost: String, - override val boundPort: Int + override val boundPort: Int, ) : PortForwarder { companion object { private val logger = LoggerFactory.getLogger(RemotePortForwarder::class.java) @@ -42,14 +44,20 @@ internal class RemotePortForwarder( remoteBindAddress: String, remoteBindPort: Int, localHost: String, - localPort: Int + localPort: Int, ): RemotePortForwarder? { val actualPort = connection.sendTcpipForwardRequest(remoteBindAddress, remoteBindPort) ?: return null val forwarder = RemotePortForwarder( - scope, connection, localHost, localPort, - remoteBindAddress, actualPort, remoteBindAddress, actualPort + scope, + connection, + localHost, + localPort, + remoteBindAddress, + actualPort, + remoteBindAddress, + actualPort ) val key = "$remoteBindAddress:$actualPort" @@ -73,7 +81,7 @@ internal class RemotePortForwarder( originPort: Int, senderChannel: Int, initialWindow: Long, - maxPacketSize: Int + maxPacketSize: Int, ) { logger.debug("Incoming forwarded-tcpip from $originAddr:$originPort, connecting to $localHost:$localPort") diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/SessionChannel.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/SessionChannel.kt index d9cacbb..7afb298 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/SessionChannel.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/SessionChannel.kt @@ -20,7 +20,10 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.runBlocking import org.connectbot.sshlib.SshSession -import org.connectbot.sshlib.protocol.* +import org.connectbot.sshlib.protocol.ByteString +import org.connectbot.sshlib.protocol.ChannelRequestPtyReq +import org.connectbot.sshlib.protocol.ChannelRequestShell +import org.connectbot.sshlib.protocol.ChannelRequestWindowChange import org.slf4j.LoggerFactory class SessionChannel internal constructor( @@ -29,7 +32,7 @@ class SessionChannel internal constructor( private var _remoteChannelNumber: Int, private val maxPacketSize: Int, private var remoteWindowSize: Long = 0, - private val initialWindowSize: Int = 64 * 1024 + private val initialWindowSize: Int = 64 * 1024, ) : SshSession { companion object { private val logger = LoggerFactory.getLogger(SessionChannel::class.java) @@ -123,13 +126,9 @@ class SessionChannel internal constructor( } } - override suspend fun read(): ByteArray? { - return _stdout.receiveCatching().getOrNull() - } + override suspend fun read(): ByteArray? = _stdout.receiveCatching().getOrNull() - override suspend fun readExtended(): Pair? { - return _extendedData.receiveCatching().getOrNull() - } + override suspend fun readExtended(): Pair? = _extendedData.receiveCatching().getOrNull() override suspend fun sendEof() { connection.sendChannelEof(_remoteChannelNumber) @@ -163,7 +162,7 @@ class SessionChannel internal constructor( heightRows: Int, widthPixels: Int, heightPixels: Int, - terminalModes: ByteArray + terminalModes: ByteArray, ): Boolean { logger.debug("Requesting PTY: $terminalType ${widthChars}x$heightRows") return connection.sendChannelRequest( diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/Socks5Handler.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/Socks5Handler.kt index c58d1c5..c62bbd8 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/Socks5Handler.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/Socks5Handler.kt @@ -16,7 +16,12 @@ package org.connectbot.sshlib.client -import io.ktor.utils.io.* +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.ByteWriteChannel +import io.ktor.utils.io.readByte +import io.ktor.utils.io.readFully +import io.ktor.utils.io.writeByte +import io.ktor.utils.io.writeFully import org.connectbot.sshlib.Socks5Authenticator import org.slf4j.LoggerFactory @@ -27,7 +32,7 @@ import org.slf4j.LoggerFactory * the target host and port for dynamic port forwarding. */ internal class Socks5Handler( - private val authenticator: Socks5Authenticator? = null + private val authenticator: Socks5Authenticator? = null, ) { companion object { private val logger = LoggerFactory.getLogger(Socks5Handler::class.java) @@ -98,7 +103,7 @@ internal class Socks5Handler( private suspend fun handleUsernamePasswordAuth( read: ByteReadChannel, - write: ByteWriteChannel + write: ByteWriteChannel, ): Boolean { val authVersion = read.readByte() if (authVersion != 0x01.toByte()) { @@ -134,7 +139,7 @@ internal class Socks5Handler( private suspend fun handleConnectRequest( read: ByteReadChannel, - write: ByteWriteChannel + write: ByteWriteChannel, ): ConnectRequest? { val ver = read.readByte() if (ver != SOCKS5_VERSION) { @@ -158,12 +163,14 @@ internal class Socks5Handler( read.readFully(addr, 0, 4) "${addr[0].toInt() and 0xFF}.${addr[1].toInt() and 0xFF}.${addr[2].toInt() and 0xFF}.${addr[3].toInt() and 0xFF}" } + ATYP_DOMAIN -> { val domainLen = read.readByte().toInt() and 0xFF val domainBytes = ByteArray(domainLen) read.readFully(domainBytes, 0, domainLen) String(domainBytes, Charsets.US_ASCII) } + ATYP_IPV6 -> { val addr = ByteArray(16) read.readFully(addr, 0, 16) @@ -174,6 +181,7 @@ internal class Socks5Handler( } } } + else -> { logger.warn("Unsupported address type: $atyp") sendReply(write, REPLY_ADDRESS_TYPE_NOT_SUPPORTED) diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/SshConnection.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/SshConnection.kt index 02435ea..86297a3 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/SshConnection.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/SshConnection.kt @@ -18,24 +18,104 @@ package org.connectbot.sshlib.client import io.kaitai.struct.ByteBufferKaitaiStream import io.kaitai.struct.KaitaiStruct -import kotlinx.coroutines.* +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.newSingleThreadContext +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext import org.connectbot.sshlib.AgentProvider import org.connectbot.sshlib.AuthHandler import org.connectbot.sshlib.AuthPublicKey -import org.connectbot.sshlib.KeyboardInteractiveCallback -import org.connectbot.sshlib.crypto.* -import org.connectbot.sshlib.protocol.* -import org.connectbot.sshlib.transport.PacketIO -import org.connectbot.sshlib.transport.Transport import org.connectbot.sshlib.HostKeyVerifier +import org.connectbot.sshlib.KeyboardInteractiveCallback import org.connectbot.sshlib.PublicKey import org.connectbot.sshlib.SshException +import org.connectbot.sshlib.crypto.CipherEntry +import org.connectbot.sshlib.crypto.CompressionEntry +import org.connectbot.sshlib.crypto.DiffieHellmanGroupExchange +import org.connectbot.sshlib.crypto.EncryptionInstance +import org.connectbot.sshlib.crypto.KexAlgorithm +import org.connectbot.sshlib.crypto.KexEntry +import org.connectbot.sshlib.crypto.KexType +import org.connectbot.sshlib.crypto.KeyDerivation +import org.connectbot.sshlib.crypto.MacEntry +import org.connectbot.sshlib.crypto.SignatureEntry +import org.connectbot.sshlib.crypto.SignatureVerifier +import org.connectbot.sshlib.crypto.SshPrivateKey +import org.connectbot.sshlib.crypto.SshPublicKeyEncoder +import org.connectbot.sshlib.protocol.AsciiString +import org.connectbot.sshlib.protocol.ChannelOpenDirectTcpip +import org.connectbot.sshlib.protocol.ChannelOpenForwardedTcpip +import org.connectbot.sshlib.protocol.ChannelOpenSession +import org.connectbot.sshlib.protocol.GlobalRequestCancelTcpipForward +import org.connectbot.sshlib.protocol.GlobalRequestResponseTcpipForward +import org.connectbot.sshlib.protocol.GlobalRequestTcpipForward +import org.connectbot.sshlib.protocol.IdBanner +import org.connectbot.sshlib.protocol.KexDhGexPayload +import org.connectbot.sshlib.protocol.KexEcdhPayload +import org.connectbot.sshlib.protocol.KexdhPayload +import org.connectbot.sshlib.protocol.SshClientCallbacks +import org.connectbot.sshlib.protocol.SshClientStateMachine +import org.connectbot.sshlib.protocol.SshEnums +import org.connectbot.sshlib.protocol.SshMsgChannelClose +import org.connectbot.sshlib.protocol.SshMsgChannelData +import org.connectbot.sshlib.protocol.SshMsgChannelEof +import org.connectbot.sshlib.protocol.SshMsgChannelExtendedData +import org.connectbot.sshlib.protocol.SshMsgChannelOpen +import org.connectbot.sshlib.protocol.SshMsgChannelOpenConfirmation +import org.connectbot.sshlib.protocol.SshMsgChannelOpenFailure +import org.connectbot.sshlib.protocol.SshMsgChannelRequest +import org.connectbot.sshlib.protocol.SshMsgChannelWindowAdjust +import org.connectbot.sshlib.protocol.SshMsgDebug +import org.connectbot.sshlib.protocol.SshMsgDisconnect +import org.connectbot.sshlib.protocol.SshMsgGlobalRequest +import org.connectbot.sshlib.protocol.SshMsgKexDhGexGroup +import org.connectbot.sshlib.protocol.SshMsgKexDhGexInit +import org.connectbot.sshlib.protocol.SshMsgKexDhGexReply +import org.connectbot.sshlib.protocol.SshMsgKexDhGexRequest +import org.connectbot.sshlib.protocol.SshMsgKexEcdhInit +import org.connectbot.sshlib.protocol.SshMsgKexEcdhReply +import org.connectbot.sshlib.protocol.SshMsgKexdhInit +import org.connectbot.sshlib.protocol.SshMsgKexdhReply +import org.connectbot.sshlib.protocol.SshMsgKexinit +import org.connectbot.sshlib.protocol.SshMsgServiceAccept +import org.connectbot.sshlib.protocol.SshMsgServiceRequest +import org.connectbot.sshlib.protocol.SshMsgUserauthBanner +import org.connectbot.sshlib.protocol.SshMsgUserauthFailure +import org.connectbot.sshlib.protocol.SshMsgUserauthInfoRequest +import org.connectbot.sshlib.protocol.SshMsgUserauthInfoResponse +import org.connectbot.sshlib.protocol.SshMsgUserauthPkOk +import org.connectbot.sshlib.protocol.SshMsgUserauthRequest +import org.connectbot.sshlib.protocol.UnencryptedPacket +import org.connectbot.sshlib.protocol.UserauthPublickeySignatureData +import org.connectbot.sshlib.protocol.UserauthRequestKeyboardInteractive +import org.connectbot.sshlib.protocol.UserauthRequestNone +import org.connectbot.sshlib.protocol.UserauthRequestPassword +import org.connectbot.sshlib.protocol.UserauthRequestPublickey +import org.connectbot.sshlib.protocol.createAsciiString +import org.connectbot.sshlib.protocol.createByteString +import org.connectbot.sshlib.protocol.createMpint +import org.connectbot.sshlib.protocol.createNameList +import org.connectbot.sshlib.protocol.createUtf8String +import org.connectbot.sshlib.protocol.toByteArray +import org.connectbot.sshlib.transport.PacketIO +import org.connectbot.sshlib.transport.Transport import org.slf4j.LoggerFactory import java.math.BigInteger import java.security.SecureRandom @@ -58,7 +138,7 @@ class SshConnection( private val hostKeyAlgorithms: String = SignatureEntry.defaultString, private val encryptionAlgorithms: String = CipherEntry.defaultString, private val macAlgorithms: String = MacEntry.defaultString, - private val compressionAlgorithms: String = CompressionEntry.defaultString + private val compressionAlgorithms: String = CompressionEntry.defaultString, ) { companion object { @@ -66,6 +146,7 @@ class SshConnection( } private val packetIO = PacketIO(transport) + @OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class) private val stateMachineDispatcher = newSingleThreadContext("ssh-state-machine") @@ -145,7 +226,7 @@ class SshConnection( private class PendingChannelOpen( val deferred: CompletableDeferred, val maxPacketSize: Int, - val initialWindowSize: Int + val initialWindowSize: Int, ) private val pendingChannelOpens = ConcurrentHashMap() private var pendingChannelRequest: CompletableDeferred? = null @@ -210,6 +291,7 @@ class SshConnection( val ecdhReply = ecdhPayload.body() as SshMsgKexEcdhReply dispatchEvent(SshClientStateMachine.SshEvent.ReceiveKex.EcdhReply(ecdhReply)) } + KexType.DH -> { val kexdhStream = ByteBufferKaitaiStream(rawBody) val kexdhPayload = KexdhPayload(kexdhStream) @@ -217,6 +299,7 @@ class SshConnection( val dhReply = kexdhPayload.body() as SshMsgKexdhReply dispatchEvent(SshClientStateMachine.SshEvent.ReceiveKex.DhReply(dhReply)) } + KexType.DH_GEX -> { // First packet is SSH_MSG_KEX_DH_GEX_GROUP val groupStream = ByteBufferKaitaiStream(rawBody) @@ -322,7 +405,7 @@ class SshConnection( */ suspend fun authenticateKeyboardInteractive( username: String, - callback: KeyboardInteractiveCallback + callback: KeyboardInteractiveCallback, ): Boolean { try { val req = SshMsgUserauthRequest().apply { @@ -364,10 +447,12 @@ class SshConnection( callback.onInfoRequest(name, instruction, prompts) { responses -> val responseMsg = SshMsgUserauthInfoResponse().apply { setNumResponses(responses.size.toLong()) - setResponses(responses.map { response -> - val bytes = response.toByteArray(Charsets.UTF_8) - createByteString(bytes) - }) + setResponses( + responses.map { response -> + val bytes = response.toByteArray(Charsets.UTF_8) + createByteString(bytes) + } + ) _check() } @@ -411,12 +496,18 @@ class SshConnection( // Build the data to sign per RFC 4252 §7 val signatureData = buildSignatureData( - sid, username, "ssh-connection", sigAlgorithmName, publicKeyBlob + sid, + username, + "ssh-connection", + sigAlgorithmName, + publicKeyBlob ) // Sign the data val signature = sigEntry.algorithm.sign( - sigAlgorithmName, privateKey.jcaKeyPair.private, signatureData + sigAlgorithmName, + privateKey.jcaKeyPair.private, + signatureData ) // Build the SSH_MSG_USERAUTH_REQUEST @@ -457,7 +548,7 @@ class SshConnection( username: String, serviceName: String, algorithmName: String, - publicKeyBlob: ByteArray + publicKeyBlob: ByteArray, ): ByteArray { val data = UserauthPublickeySignatureData().apply { setSessionIdentifier(createByteString(sessionId)) @@ -494,7 +585,7 @@ class SshConnection( private suspend fun doAuthenticate( username: String, handler: AuthHandler, - channel: Channel + channel: Channel, ): Boolean { // Step 1: Send "none" auth to discover allowed methods currentAuthMethod = "none" @@ -543,7 +634,7 @@ class SshConnection( private suspend fun probePublicKey( username: String, key: AuthPublicKey, - channel: Channel + channel: Channel, ): AuthResult { currentAuthMethod = "publickey" sendAuthRequest(username, "publickey") { @@ -562,11 +653,15 @@ class SshConnection( username: String, key: AuthPublicKey, handler: AuthHandler, - channel: Channel + channel: Channel, ): Boolean { val sid = sessionId ?: throw SshException("Session ID not established") val signatureData = buildSignatureData( - sid, username, "ssh-connection", key.algorithmName, key.publicKeyBlob + sid, + username, + "ssh-connection", + key.algorithmName, + key.publicKeyBlob ) val signature = handler.onSignatureRequest(key, signatureData) ?: return false @@ -592,7 +687,7 @@ class SshConnection( private suspend fun doKeyboardInteractive( username: String, handler: AuthHandler, - channel: Channel + channel: Channel, ): Boolean { currentAuthMethod = "keyboard-interactive" sendAuthRequest(username, "keyboard-interactive") { @@ -607,17 +702,23 @@ class SshConnection( while (true) { when (val result = channel.receive()) { is AuthResult.Success -> return true + is AuthResult.Failure -> return false + is AuthResult.InfoRequest -> { val responses = handler.onKeyboardInteractivePrompt( - result.name, result.instruction, result.prompts + result.name, + result.instruction, + result.prompts ) ?: return false val responseMsg = SshMsgUserauthInfoResponse().apply { setNumResponses(responses.size.toLong()) - setResponses(responses.map { response -> - createByteString(response.toByteArray(Charsets.UTF_8)) - }) + setResponses( + responses.map { response -> + createByteString(response.toByteArray(Charsets.UTF_8)) + } + ) _check() } @@ -626,6 +727,7 @@ class SshConnection( responseMsg.toByteArray() ) } + is AuthResult.PkOk -> { // Unexpected during keyboard-interactive return false @@ -637,7 +739,7 @@ class SshConnection( private suspend fun doPasswordAuth( username: String, password: String, - channel: Channel + channel: Channel, ): Boolean { currentAuthMethod = "password" sendAuthRequest(username, "password") { @@ -658,7 +760,7 @@ class SshConnection( private suspend fun sendAuthRequest( username: String, method: String, - configure: SshMsgUserauthRequest.() -> Unit + configure: SshMsgUserauthRequest.() -> Unit, ) { val req = SshMsgUserauthRequest().apply { setUserName(createAsciiString(username)) @@ -1229,9 +1331,11 @@ class SshConnection( ) logger.info("Accepted agent channel: local=$localChannelNumber, remote=$senderChannel") } + "forwarded-tcpip" -> { handleForwardedTcpip(msg, senderChannel, initialWindow, maxPacketSize) } + else -> { rejectChannelOpen(senderChannel, channelType) } @@ -1245,7 +1349,7 @@ class SshConnection( msg: SshMsgChannelOpen, senderChannel: Int, initialWindow: Long, - maxPacketSize: Int + maxPacketSize: Int, ) { try { val channelData = msg.channelSpecificData() @@ -1289,7 +1393,7 @@ class SshConnection( recipientChannel: Int, senderChannel: Int, initialWindowSize: Int, - maximumPacketSize: Int + maximumPacketSize: Int, ) { val msg = SshMsgChannelOpenConfirmation() msg.setRecipientChannel(recipientChannel.toLong()) @@ -1308,7 +1412,7 @@ class SshConnection( recipientChannel: Int, reasonCode: Int, description: String, - languageTag: String + languageTag: String, ) { val msg = SshMsgChannelOpenFailure() msg.setRecipientChannel(recipientChannel.toLong()) @@ -1427,7 +1531,7 @@ class SshConnection( originAddr: String, originPort: Int, initialWindowSize: Int = 256 * 1024, - maxPacketSize: Int = 32 * 1024 + maxPacketSize: Int = 32 * 1024, ): ForwardingChannel? { val localChannelNumber = allocateChannelNumber() @@ -1550,7 +1654,7 @@ class SshConnection( recipientChannel: Int, senderChannel: Int, initialWindowSize: Int, - maximumPacketSize: Int + maximumPacketSize: Int, ) { sendChannelOpenConfirmation(recipientChannel, senderChannel, initialWindowSize, maximumPacketSize) } @@ -1559,7 +1663,7 @@ class SshConnection( recipientChannel: Int, reasonCode: Int, description: String, - languageTag: String + languageTag: String, ) { sendChannelOpenFailure(recipientChannel, reasonCode, description, languageTag) } @@ -1578,9 +1682,11 @@ class SshConnection( SshEnums.MessageType.SSH_MSG_IGNORE -> { dispatchEvent(SshClientStateMachine.SshEvent.ReceiveIgnore) } + SshEnums.MessageType.SSH_MSG_DEBUG -> { dispatchEvent(SshClientStateMachine.SshEvent.ReceiveDebug(packet.body() as SshMsgDebug)) } + SshEnums.MessageType.SSH_MSG_GLOBAL_REQUEST -> { try { val rawBody = packet._raw_body() @@ -1592,9 +1698,11 @@ class SshConnection( logger.error("Failed to parse SSH_MSG_GLOBAL_REQUEST", e) } } + SshEnums.MessageType.SSH_MSG_CHANNEL_OPEN -> { handleIncomingChannelOpen(packet) } + SshEnums.MessageType.SSH_MSG_CHANNEL_OPEN_CONFIRMATION -> { val confirmationMsg = parseBody(packet) val recipientChannel = confirmationMsg.recipientChannel().toInt() @@ -1617,6 +1725,7 @@ class SshConnection( dispatchEvent(SshClientStateMachine.SshEvent.ReceiveChannelOpenConfirmation(confirmationMsg)) } } + SshEnums.MessageType.SSH_MSG_CHANNEL_OPEN_FAILURE -> { val failureMsg = parseBody(packet) val recipientChannel = failureMsg.recipientChannel().toInt() @@ -1627,6 +1736,7 @@ class SshConnection( dispatchEvent(SshClientStateMachine.SshEvent.ReceiveChannelOpenFailure(failureMsg)) } } + SshEnums.MessageType.SSH_MSG_CHANNEL_DATA -> { val msg = parseBody(packet) val recipientChannel = msg.recipientChannel().toInt() @@ -1640,6 +1750,7 @@ class SshConnection( else -> logger.warn("Data for unknown channel $recipientChannel") } } + SshEnums.MessageType.SSH_MSG_CHANNEL_EXTENDED_DATA -> { val msg = parseBody(packet) val channel = channelsByRemote[msg.recipientChannel().toInt()] @@ -1649,6 +1760,7 @@ class SshConnection( logger.warn("Extended data for unknown channel ${msg.recipientChannel()}") } } + SshEnums.MessageType.SSH_MSG_CHANNEL_WINDOW_ADJUST -> { val msg = parseBody(packet) val recipientChannel = msg.recipientChannel().toInt() @@ -1662,6 +1774,7 @@ class SshConnection( else -> logger.warn("Window adjust for unknown channel $recipientChannel") } } + SshEnums.MessageType.SSH_MSG_CHANNEL_EOF -> { val msg = parseBody(packet) val recipientChannel = msg.recipientChannel().toInt() @@ -1675,6 +1788,7 @@ class SshConnection( else -> logger.warn("EOF for unknown channel $recipientChannel") } } + SshEnums.MessageType.SSH_MSG_CHANNEL_CLOSE -> { val msg = parseBody(packet) val recipientChannel = msg.recipientChannel().toInt() @@ -1683,15 +1797,19 @@ class SshConnection( val fwdChannel = forwardingChannelsByRemote[recipientChannel] when { channel != null -> channel.onClose() + agentChannel != null -> agentChannel.onClose() + fwdChannel != null -> { fwdChannel.onClose() unregisterForwardingChannel(fwdChannel) } + else -> logger.warn("Close for unknown channel $recipientChannel") } checkAllChannelsClosed() } + SshEnums.MessageType.SSH_MSG_CHANNEL_REQUEST -> { val rawBody = packet._raw_body() val stream = ByteBufferKaitaiStream(rawBody) @@ -1699,12 +1817,15 @@ class SshConnection( msg._read() logger.debug("Received channel request: ${msg.requestType().value()} (want_reply=${msg.wantReply() != 0})") } + SshEnums.MessageType.SSH_MSG_CHANNEL_SUCCESS -> { dispatchEvent(SshClientStateMachine.SshEvent.ReceiveChannelSuccess) } + SshEnums.MessageType.SSH_MSG_CHANNEL_FAILURE -> { dispatchEvent(SshClientStateMachine.SshEvent.ReceiveChannelFailure) } + SshEnums.MessageType.SSH_MSG_USERAUTH_SUCCESS -> { val ch = authResultChannel if (ch != null) { @@ -1712,6 +1833,7 @@ class SshConnection( } dispatchEvent(SshClientStateMachine.SshEvent.AuthenticationSuccess) } + SshEnums.MessageType.SSH_MSG_USERAUTH_FAILURE -> { val ch = authResultChannel if (ch != null) { @@ -1723,18 +1845,22 @@ class SshConnection( dispatchEvent(SshClientStateMachine.SshEvent.AuthenticationFailure) } } + SshEnums.MessageType.SSH_MSG_USERAUTH_BANNER -> { val msg = parseBody(packet) dispatchEvent(SshClientStateMachine.SshEvent.ReceiveUserauthBanner(msg)) } + SshEnums.MessageType.SSH_MSG_USERAUTH_METHOD_SPECIFIC_60 -> { val ch = authResultChannel if (ch != null && currentAuthMethod == "publickey") { val msg = parseBody(packet) - ch.trySend(AuthResult.PkOk( - msg.publicKeyAlgorithmName().value(), - msg.publicKeyBlob().data() - )) + ch.trySend( + AuthResult.PkOk( + msg.publicKeyAlgorithmName().value(), + msg.publicKeyBlob().data() + ) + ) } else if (ch != null && currentAuthMethod == "keyboard-interactive") { val msg = parseBody(packet) val name = String(msg.name().data(), Charsets.UTF_8) @@ -1751,6 +1877,7 @@ class SshConnection( dispatchEvent(SshClientStateMachine.SshEvent.ReceiveUserauthInfoRequest(msg)) } } + SshEnums.MessageType.SSH_MSG_REQUEST_SUCCESS -> { val pending = pendingGlobalRequest if (pending != null) { @@ -1761,6 +1888,7 @@ class SshConnection( logger.warn("Received REQUEST_SUCCESS with no pending global request") } } + SshEnums.MessageType.SSH_MSG_REQUEST_FAILURE -> { val pending = pendingGlobalRequest if (pending != null) { @@ -1770,9 +1898,11 @@ class SshConnection( logger.warn("Received REQUEST_FAILURE with no pending global request") } } + SshEnums.MessageType.SSH_MSG_DISCONNECT -> { dispatchEvent(SshClientStateMachine.SshEvent.Disconnect) } + else -> { logger.warn("Unhandled message type: ${packet.messageType()}") } @@ -1794,13 +1924,16 @@ class SshConnection( logger.debug("Received SSH_MSG_IGNORE, skipping") continue } + SshEnums.MessageType.SSH_MSG_DEBUG -> { logger.debug("Received SSH_MSG_DEBUG, skipping") continue } + expectedType -> { return packet.body() as T } + else -> { throw SshException("Expected $expectedType but got $messageType") } @@ -1821,13 +1954,16 @@ class SshConnection( logger.debug("Received SSH_MSG_IGNORE, skipping") continue } + SshEnums.MessageType.SSH_MSG_DEBUG -> { logger.debug("Received SSH_MSG_DEBUG, skipping") continue } + in expectedTypes -> { return packet as T } + else -> { throw SshException("Expected one of ${expectedTypes.joinToString()} but got $messageType") } @@ -1927,8 +2063,12 @@ class SshConnection( } _disconnectedFlow.tryEmit(e) } finally { - for (ch in channels.values) { ch.onClose() } - for (ch in forwardingChannels.values) { ch.onClose() } + for (ch in channels.values) { + ch.onClose() + } + for (ch in forwardingChannels.values) { + ch.onClose() + } } } } @@ -1946,8 +2086,8 @@ class SshConnection( * @return SessionChannel instance if successful, null otherwise */ suspend fun openSessionChannel( - initialWindowSize: Int = 64 * 1024, // 64KiB - maxPacketSize: Int = 32 * 1024 // 32KiB + initialWindowSize: Int = 64 * 1024, + maxPacketSize: Int = 32 * 1024, ): SessionChannel? { val localChannelNumber = allocateChannelNumber() @@ -1956,12 +2096,14 @@ class SshConnection( val deferred = CompletableDeferred() pendingChannelOpen = deferred - dispatchEvent(SshClientStateMachine.SshEvent.OpenChannel( - channelType = "session", - localChannelNumber = localChannelNumber, - initialWindowSize = initialWindowSize, - maxPacketSize = maxPacketSize - )) + dispatchEvent( + SshClientStateMachine.SshEvent.OpenChannel( + channelType = "session", + localChannelNumber = localChannelNumber, + initialWindowSize = initialWindowSize, + maxPacketSize = maxPacketSize + ) + ) val confirmationMsg = deferred.await() ?: return null @@ -1996,7 +2138,7 @@ class SshConnection( recipientChannel: Int, requestType: String, wantReply: Boolean, - configureRequest: (SshMsgChannelRequest) -> Unit + configureRequest: (SshMsgChannelRequest) -> Unit, ): Boolean { val msg = SshMsgChannelRequest().apply { setRecipientChannel(recipientChannel.toLong()) @@ -2009,14 +2151,18 @@ class SshConnection( val deferred = if (wantReply) { CompletableDeferred().also { pendingChannelRequest = it } - } else null - - dispatchEvent(SshClientStateMachine.SshEvent.SendChannelRequest( - recipientChannel = recipientChannel, - requestType = requestType, - wantReply = wantReply, - message = msg - )) + } else { + null + } + + dispatchEvent( + SshClientStateMachine.SshEvent.SendChannelRequest( + recipientChannel = recipientChannel, + requestType = requestType, + wantReply = wantReply, + message = msg + ) + ) if (deferred == null) { return true diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/StreamForwarder.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/StreamForwarder.kt index b4423df..3e02e61 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/StreamForwarder.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/StreamForwarder.kt @@ -16,14 +16,15 @@ package org.connectbot.sshlib.client -import io.ktor.utils.io.* +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.ByteWriteChannel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import org.slf4j.LoggerFactory internal class StreamForwarder private constructor( - private val dataForwarder: DataForwarder + private val dataForwarder: DataForwarder, ) : org.connectbot.sshlib.StreamForwarder { companion object { private val logger = LoggerFactory.getLogger(StreamForwarder::class.java) @@ -35,10 +36,13 @@ internal class StreamForwarder private constructor( remoteHost: String, remotePort: Int, originAddr: String = "127.0.0.1", - originPort: Int = 0 + originPort: Int = 0, ): StreamForwarder? { val sshChannel = connection.openDirectTcpipChannel( - remoteHost, remotePort, originAddr, originPort + remoteHost, + remotePort, + originAddr, + originPort ) if (sshChannel == null) { logger.warn("Failed to open direct-tcpip channel for $remoteHost:$remotePort") diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/AesCbcCipher.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/AesCbcCipher.kt index ea3a148..14c1bca 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/AesCbcCipher.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/AesCbcCipher.kt @@ -35,7 +35,7 @@ import javax.crypto.spec.SecretKeySpec internal class AesCbcCipher( private val key: ByteArray, private val iv: ByteArray, - forEncryption: Boolean + forEncryption: Boolean, ) : PacketCipher { override val blockSize: Int = 16 @@ -55,11 +55,7 @@ internal class AesCbcCipher( cipher.init(mode, keySpec, ivSpec) } - override fun encrypt(data: ByteArray): ByteArray { - return cipher.update(data) - } + override fun encrypt(data: ByteArray): ByteArray = cipher.update(data) - override fun decrypt(data: ByteArray): ByteArray { - return cipher.update(data) - } + override fun decrypt(data: ByteArray): ByteArray = cipher.update(data) } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/AesCtrCipher.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/AesCtrCipher.kt index 548de45..64c5073 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/AesCtrCipher.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/AesCtrCipher.kt @@ -35,7 +35,7 @@ import javax.crypto.spec.SecretKeySpec internal class AesCtrCipher( private val key: ByteArray, private val iv: ByteArray, - forEncryption: Boolean + forEncryption: Boolean, ) : PacketCipher { override val blockSize: Int = 16 @@ -55,11 +55,7 @@ internal class AesCtrCipher( cipher.init(mode, keySpec, ivSpec) } - override fun encrypt(data: ByteArray): ByteArray { - return cipher.update(data) - } + override fun encrypt(data: ByteArray): ByteArray = cipher.update(data) - override fun decrypt(data: ByteArray): ByteArray { - return cipher.update(data) - } + override fun decrypt(data: ByteArray): ByteArray = cipher.update(data) } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/AesGcmCipher.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/AesGcmCipher.kt index ccd4633..65f4619 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/AesGcmCipher.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/AesGcmCipher.kt @@ -36,7 +36,7 @@ import javax.crypto.spec.SecretKeySpec */ internal class AesGcmCipher( key: ByteArray, - private val iv: ByteArray + private val iv: ByteArray, ) : PacketAead { override val tagLength: Int = 16 diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Algorithms.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Algorithms.kt index a349f04..a946a39 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Algorithms.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Algorithms.kt @@ -27,51 +27,82 @@ internal enum class CipherEntry( val ivLength: Int, val blockSize: Int, val isAead: Boolean, - private val factory: (key: ByteArray, iv: ByteArray, forEncryption: Boolean) -> EncryptionInstance + private val factory: (key: ByteArray, iv: ByteArray, forEncryption: Boolean) -> EncryptionInstance, ) { CHACHA20_POLY1305( - "chacha20-poly1305@openssh.com", 64, 0, 8, true, + "chacha20-poly1305@openssh.com", + 64, + 0, + 8, + true, { key, _, _ -> EncryptionInstance.Aead(ChaCha20Poly1305Cipher(key)) } ), AES128_GCM( - "aes128-gcm@openssh.com", 16, 12, 16, true, + "aes128-gcm@openssh.com", + 16, + 12, + 16, + true, { key, iv, _ -> EncryptionInstance.Aead(AesGcmCipher(key, iv)) } ), AES256_GCM( - "aes256-gcm@openssh.com", 32, 12, 16, true, + "aes256-gcm@openssh.com", + 32, + 12, + 16, + true, { key, iv, _ -> EncryptionInstance.Aead(AesGcmCipher(key, iv)) } ), AES128_CTR( - "aes128-ctr", 16, 16, 16, false, + "aes128-ctr", + 16, + 16, + 16, + false, { key, iv, enc -> EncryptionInstance.Cipher(AesCtrCipher(key, iv, enc)) } ), AES256_CTR( - "aes256-ctr", 32, 16, 16, false, + "aes256-ctr", + 32, + 16, + 16, + false, { key, iv, enc -> EncryptionInstance.Cipher(AesCtrCipher(key, iv, enc)) } ), AES128_CBC( - "aes128-cbc", 16, 16, 16, false, + "aes128-cbc", + 16, + 16, + 16, + false, { key, iv, enc -> EncryptionInstance.Cipher(AesCbcCipher(key, iv, enc)) } ), AES256_CBC( - "aes256-cbc", 32, 16, 16, false, + "aes256-cbc", + 32, + 16, + 16, + false, { key, iv, enc -> EncryptionInstance.Cipher(AesCbcCipher(key, iv, enc)) } ), TRIPLE_DES_CBC( - "3des-cbc", 24, 8, 8, false, + "3des-cbc", + 24, + 8, + 8, + false, { key, iv, enc -> EncryptionInstance.Cipher(TripleDesCbcCipher(key, iv, enc)) } - ); + ), + ; - internal fun create(key: ByteArray, iv: ByteArray, forEncryption: Boolean): EncryptionInstance = - factory(key, iv, forEncryption) + internal fun create(key: ByteArray, iv: ByteArray, forEncryption: Boolean): EncryptionInstance = factory(key, iv, forEncryption) companion object { val defaults: List = entries.toList() val defaultString: String = defaults.joinToString(",") { it.sshName } - fun fromSshName(name: String): CipherEntry? = - entries.firstOrNull { it.sshName == name } + fun fromSshName(name: String): CipherEntry? = entries.firstOrNull { it.sshName == name } } } @@ -80,32 +111,51 @@ internal enum class MacEntry( val keyLength: Int, val macLength: Int, val isEtm: Boolean, - private val factory: (key: ByteArray) -> PacketMac + private val factory: (key: ByteArray) -> PacketMac, ) { HMAC_SHA2_256_ETM( - "hmac-sha2-256-etm@openssh.com", 32, 32, true, + "hmac-sha2-256-etm@openssh.com", + 32, + 32, + true, { key -> HmacSha256(key.copyOf(32)) } ), HMAC_SHA2_512_ETM( - "hmac-sha2-512-etm@openssh.com", 64, 64, true, + "hmac-sha2-512-etm@openssh.com", + 64, + 64, + true, { key -> HmacSha512(key.copyOf(64)) } ), HMAC_SHA2_256( - "hmac-sha2-256", 32, 32, false, + "hmac-sha2-256", + 32, + 32, + false, { key -> HmacSha256(key.copyOf(32)) } ), HMAC_SHA2_512( - "hmac-sha2-512", 64, 64, false, + "hmac-sha2-512", + 64, + 64, + false, { key -> HmacSha512(key.copyOf(64)) } ), HMAC_SHA1_ETM( - "hmac-sha1-etm@openssh.com", 20, 20, true, + "hmac-sha1-etm@openssh.com", + 20, + 20, + true, { key -> HmacSha1(key.copyOf(20)) } ), HMAC_SHA1( - "hmac-sha1", 20, 20, false, + "hmac-sha1", + 20, + 20, + false, { key -> HmacSha1(key.copyOf(20)) } - ); + ), + ; internal fun create(key: ByteArray): PacketMac = factory(key) @@ -114,8 +164,7 @@ internal enum class MacEntry( val defaultString: String = defaults.joinToString(",") { it.sshName } - fun fromSshName(name: String): MacEntry? = - entries.firstOrNull { it.sshName == name } + fun fromSshName(name: String): MacEntry? = entries.firstOrNull { it.sshName == name } } } @@ -125,56 +174,81 @@ internal enum class KexEntry( val sshName: String, val hashAlgorithm: String, val type: KexType, - private val factory: () -> KexAlgorithm + private val factory: () -> KexAlgorithm, ) { MLKEM768X25519_SHA256( - "mlkem768x25519-sha256", "SHA-256", KexType.ECDH, + "mlkem768x25519-sha256", + "SHA-256", + KexType.ECDH, { MlKemHybridKeyExchange() } ), CURVE25519_SHA256( - "curve25519-sha256", "SHA-256", KexType.ECDH, + "curve25519-sha256", + "SHA-256", + KexType.ECDH, { Curve25519KeyExchange() } ), ECDH_SHA2_NISTP256( - "ecdh-sha2-nistp256", "SHA-256", KexType.ECDH, + "ecdh-sha2-nistp256", + "SHA-256", + KexType.ECDH, { EcdhKeyExchange("nistp256") } ), ECDH_SHA2_NISTP384( - "ecdh-sha2-nistp384", "SHA-384", KexType.ECDH, + "ecdh-sha2-nistp384", + "SHA-384", + KexType.ECDH, { EcdhKeyExchange("nistp384") } ), ECDH_SHA2_NISTP521( - "ecdh-sha2-nistp521", "SHA-512", KexType.ECDH, + "ecdh-sha2-nistp521", + "SHA-512", + KexType.ECDH, { EcdhKeyExchange("nistp521") } ), DH_GROUP18_SHA512( - "diffie-hellman-group18-sha512", "SHA-512", KexType.DH, + "diffie-hellman-group18-sha512", + "SHA-512", + KexType.DH, { DiffieHellman("SHA-512", DhGroups.GROUP18_P, DhGroups.GENERATOR) } ), DH_GROUP16_SHA512( - "diffie-hellman-group16-sha512", "SHA-512", KexType.DH, + "diffie-hellman-group16-sha512", + "SHA-512", + KexType.DH, { DiffieHellman("SHA-512", DhGroups.GROUP16_P, DhGroups.GENERATOR) } ), DH_GROUP_EXCHANGE_SHA256( - "diffie-hellman-group-exchange-sha256", "SHA-256", KexType.DH_GEX, + "diffie-hellman-group-exchange-sha256", + "SHA-256", + KexType.DH_GEX, { DiffieHellmanGroupExchange("SHA-256") } ), DH_GROUP14_SHA256( - "diffie-hellman-group14-sha256", "SHA-256", KexType.DH, + "diffie-hellman-group14-sha256", + "SHA-256", + KexType.DH, { DiffieHellman("SHA-256", DhGroups.GROUP14_P, DhGroups.GENERATOR) } ), DH_GROUP14_SHA1( - "diffie-hellman-group14-sha1", "SHA-1", KexType.DH, + "diffie-hellman-group14-sha1", + "SHA-1", + KexType.DH, { DiffieHellman("SHA-1", DhGroups.GROUP14_P, DhGroups.GENERATOR) } ), DH_GROUP_EXCHANGE_SHA1( - "diffie-hellman-group-exchange-sha1", "SHA-1", KexType.DH_GEX, + "diffie-hellman-group-exchange-sha1", + "SHA-1", + KexType.DH_GEX, { DiffieHellmanGroupExchange("SHA-1") } ), DH_GROUP1_SHA1( - "diffie-hellman-group1-sha1", "SHA-1", KexType.DH, + "diffie-hellman-group1-sha1", + "SHA-1", + KexType.DH, { DiffieHellman("SHA-1", DhGroups.GROUP1_P, DhGroups.GENERATOR) } - ); + ), + ; internal fun create(): KexAlgorithm = factory() @@ -184,14 +258,13 @@ internal enum class KexEntry( val defaultString: String = defaults.joinToString(",") { it.sshName } + ",kex-strict-c-v00@openssh.com" - fun fromSshName(name: String): KexEntry? = - entries.firstOrNull { it.sshName == name } + fun fromSshName(name: String): KexEntry? = entries.firstOrNull { it.sshName == name } } } internal enum class SignatureEntry( val sshName: String, - internal val algorithm: SshSignatureAlgorithm + internal val algorithm: SshSignatureAlgorithm, ) { SSH_ED25519("ssh-ed25519", Ed25519SignatureAlgorithm), SSH_ED448("ssh-ed448", Ed448SignatureAlgorithm), @@ -200,26 +273,27 @@ internal enum class SignatureEntry( ECDSA_SHA2_NISTP521("ecdsa-sha2-nistp521", EcdsaSignatureAlgorithm), RSA_SHA2_256("rsa-sha2-256", RsaSignatureAlgorithm), RSA_SHA2_512("rsa-sha2-512", RsaSignatureAlgorithm), - SSH_RSA("ssh-rsa", RsaSignatureAlgorithm); + SSH_RSA("ssh-rsa", RsaSignatureAlgorithm), + ; companion object { val defaults: List = entries.toList() val defaultString: String = defaults.joinToString(",") { it.sshName } - fun fromSshName(name: String): SignatureEntry? = - entries.firstOrNull { it.sshName == name } + fun fromSshName(name: String): SignatureEntry? = entries.firstOrNull { it.sshName == name } } } internal enum class CompressionEntry( val sshName: String, val delayedActivation: Boolean, - private val factory: () -> PacketCompressor? + private val factory: () -> PacketCompressor?, ) { ZLIB_OPENSSH("zlib@openssh.com", delayedActivation = true, { ZlibCompressor() }), ZLIB("zlib", delayedActivation = false, { ZlibCompressor() }), - NONE("none", delayedActivation = false, { null }); + NONE("none", delayedActivation = false, { null }), + ; internal fun create(): PacketCompressor? = factory() @@ -230,7 +304,6 @@ internal enum class CompressionEntry( val enabledString: String = entries.joinToString(",") { it.sshName } - fun fromSshName(name: String): CompressionEntry? = - entries.firstOrNull { it.sshName == name } + fun fromSshName(name: String): CompressionEntry? = entries.firstOrNull { it.sshName == name } } } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Curve25519KeyExchange.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Curve25519KeyExchange.kt index 5cf7992..0fd2351 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Curve25519KeyExchange.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Curve25519KeyExchange.kt @@ -21,7 +21,7 @@ import java.math.BigInteger internal class Curve25519KeyExchange( private val x25519Provider: X25519Provider = X25519ProviderFactory.provider, - private val presetPrivateKey: ByteArray? = null + private val presetPrivateKey: ByteArray? = null, ) : KexAlgorithm { override val hashAlgorithm: String = "SHA-256" diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/DhGroups.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/DhGroups.kt index 8a54bae..da5a3fd 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/DhGroups.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/DhGroups.kt @@ -27,98 +27,102 @@ internal object DhGroups { /** 1024-bit MODP Group (RFC 2409 Section 6.2, Oakley Group 2) */ val GROUP1_P = BigInteger( "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + - "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + - "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + - "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + - "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" + - "FFFFFFFFFFFFFFFF", 16 + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" + + "FFFFFFFFFFFFFFFF", + 16 ) /** 2048-bit MODP Group (RFC 3526 Section 3) */ val GROUP14_P = BigInteger( "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + - "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + - "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + - "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + - "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + - "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + - "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + - "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + - "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + - "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + - "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16 + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", + 16 ) /** 4096-bit MODP Group (RFC 3526 Section 5) */ val GROUP16_P = BigInteger( "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + - "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + - "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + - "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + - "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + - "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + - "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + - "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + - "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + - "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + - "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + - "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + - "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + - "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + - "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + - "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + - "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + - "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + - "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + - "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + - "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" + - "FFFFFFFFFFFFFFFF", 16 + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" + + "FFFFFFFFFFFFFFFF", + 16 ) /** 8192-bit MODP Group (RFC 3526 Section 7) */ val GROUP18_P = BigInteger( "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + - "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + - "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + - "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + - "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + - "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + - "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + - "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + - "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + - "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + - "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + - "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + - "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + - "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + - "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + - "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + - "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + - "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + - "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + - "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + - "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + - "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" + - "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" + - "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" + - "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" + - "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" + - "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" + - "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + - "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" + - "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" + - "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" + - "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" + - "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" + - "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" + - "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" + - "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" + - "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" + - "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" + - "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" + - "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" + - "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" + - "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + - "60C980DD98EDD3DFFFFFFFFFFFFFFFFF", 16 + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" + + "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" + + "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" + + "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" + + "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" + + "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" + + "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" + + "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" + + "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" + + "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" + + "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" + + "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" + + "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" + + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" + + "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" + + "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" + + "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" + + "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" + + "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" + + "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF", + 16 ) } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/DiffieHellman.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/DiffieHellman.kt index 3397896..49f1811 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/DiffieHellman.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/DiffieHellman.kt @@ -17,7 +17,6 @@ package org.connectbot.sshlib.crypto import org.connectbot.sshlib.SshException - import java.math.BigInteger import java.security.SecureRandom @@ -27,7 +26,7 @@ import java.security.SecureRandom internal class DiffieHellman( override val hashAlgorithm: String = "SHA-256", private val p: BigInteger = DhGroups.GROUP14_P, - private val g: BigInteger = DhGroups.GENERATOR + private val g: BigInteger = DhGroups.GENERATOR, ) : KexAlgorithm { companion object { private val secureRandom = SecureRandom() diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/DiffieHellmanGroupExchange.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/DiffieHellmanGroupExchange.kt index c74403a..c0a2b67 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/DiffieHellmanGroupExchange.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/DiffieHellmanGroupExchange.kt @@ -82,7 +82,7 @@ internal class DiffieHellmanGroupExchange(override val hashAlgorithm: String) : serverHostKey: ByteArray, clientPublicKey: ByteArray, serverPublicKey: ByteArray, - sharedSecret: ByteArray + sharedSecret: ByteArray, ): ByteArray { val p = this.p ?: throw SshException("DH group not set; call setGroup() first") val g = this.g ?: throw SshException("DH group not set; call setGroup() first") @@ -91,22 +91,26 @@ internal class DiffieHellmanGroupExchange(override val hashAlgorithm: String) : fun writeString(data: ByteArray) { val len = data.size - transcript.write(byteArrayOf( - (len shr 24).toByte(), - (len shr 16).toByte(), - (len shr 8).toByte(), - len.toByte() - )) + transcript.write( + byteArrayOf( + (len shr 24).toByte(), + (len shr 16).toByte(), + (len shr 8).toByte(), + len.toByte() + ) + ) transcript.write(data) } fun writeUint32(value: Int) { - transcript.write(byteArrayOf( - (value shr 24).toByte(), - (value shr 16).toByte(), - (value shr 8).toByte(), - value.toByte() - )) + transcript.write( + byteArrayOf( + (value shr 24).toByte(), + (value shr 16).toByte(), + (value shr 8).toByte(), + value.toByte() + ) + ) } writeString(clientVersion) diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/EcdhKeyExchange.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/EcdhKeyExchange.kt index c417b46..fcd869e 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/EcdhKeyExchange.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/EcdhKeyExchange.kt @@ -106,13 +106,17 @@ internal class EcdhKeyExchange(private val curveName: String) : KexAlgorithm { val result = ByteArray(1 + 2 * fieldSize) result[0] = 0x04 System.arraycopy( - x, maxOf(0, x.size - fieldSize), - result, 1 + fieldSize - minOf(x.size, fieldSize), + x, + maxOf(0, x.size - fieldSize), + result, + 1 + fieldSize - minOf(x.size, fieldSize), minOf(x.size, fieldSize) ) System.arraycopy( - y, maxOf(0, y.size - fieldSize), - result, 1 + 2 * fieldSize - minOf(y.size, fieldSize), + y, + maxOf(0, y.size - fieldSize), + result, + 1 + 2 * fieldSize - minOf(y.size, fieldSize), minOf(y.size, fieldSize) ) return result diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/EcdsaSignatureAlgorithm.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/EcdsaSignatureAlgorithm.kt index d90c6e4..bc882f2 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/EcdsaSignatureAlgorithm.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/EcdsaSignatureAlgorithm.kt @@ -17,12 +17,10 @@ package org.connectbot.sshlib.crypto import org.connectbot.sshlib.SshException - import org.connectbot.sshlib.protocol.EcdsaPublicKeyBlob import org.connectbot.sshlib.protocol.EcdsaSignatureBlob import org.connectbot.sshlib.protocol.SshPublicKey import org.connectbot.sshlib.protocol.SshSignature -import java.io.ByteArrayOutputStream import java.math.BigInteger import java.security.AlgorithmParameters import java.security.KeyFactory @@ -75,12 +73,10 @@ internal object EcdsaSignatureAlgorithm : SshSignatureAlgorithm { return ECPoint(x, y) } - internal fun encodeDerEcdsaSignature(r: BigInteger, s: BigInteger): ByteArray { - return encodeDer { - sequence { - integer(r) - integer(s) - } + internal fun encodeDerEcdsaSignature(r: BigInteger, s: BigInteger): ByteArray = encodeDer { + sequence { + integer(r) + integer(s) } } @@ -113,6 +109,6 @@ internal object EcdsaSignatureAlgorithm : SshSignatureAlgorithm { val sshSigBlob = encodeMpint(r.toByteArray()) + encodeMpint(s.toByteArray()) return encodeSshString(algorithmName.toByteArray(Charsets.US_ASCII)) + - encodeSshString(sshSigBlob) + encodeSshString(sshSigBlob) } } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Ed25519SignatureAlgorithm.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Ed25519SignatureAlgorithm.kt index e2a99c8..205ec76 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Ed25519SignatureAlgorithm.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Ed25519SignatureAlgorithm.kt @@ -55,6 +55,6 @@ internal object Ed25519SignatureAlgorithm : SshSignatureAlgorithm { signer.update(data) val sigBytes = signer.sign() return encodeSshString("ssh-ed25519".toByteArray(Charsets.US_ASCII)) + - encodeSshString(sigBytes) + encodeSshString(sigBytes) } } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Ed448SignatureAlgorithm.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Ed448SignatureAlgorithm.kt index af856f7..26f5640 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Ed448SignatureAlgorithm.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Ed448SignatureAlgorithm.kt @@ -55,6 +55,6 @@ internal object Ed448SignatureAlgorithm : SshSignatureAlgorithm { signer.update(data) val sigBytes = signer.sign() return encodeSshString("ssh-ed448".toByteArray(Charsets.US_ASCII)) + - encodeSshString(sigBytes) + encodeSshString(sigBytes) } } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/JavaMlKemProvider.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/JavaMlKemProvider.kt index a2beb6b..e7e4b6c 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/JavaMlKemProvider.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/JavaMlKemProvider.kt @@ -32,12 +32,12 @@ internal class JavaMlKemProvider : MlKemProvider { // X.509 wrapper for ML-KEM-768 public keys private val X509_PREFIX = byteArrayOf( - 0x30, 0x82.toByte(), 0x04, 0xb2.toByte(), // SEQUENCE - 0x30, 0x0b, // SEQUENCE - 0x06, 0x09, // OID + 0x30, 0x82.toByte(), 0x04, 0xb2.toByte(), // SEQUENCE + 0x30, 0x0b, // SEQUENCE + 0x06, 0x09, // OID 0x60, 0x86.toByte(), 0x48, 0x01, 0x65, 0x03, 0x04, 0x04, 0x02, - 0x03, 0x82.toByte(), 0x04, 0xa1.toByte(), // BIT STRING - 0x00 // no unused bits + 0x03, 0x82.toByte(), 0x04, 0xa1.toByte(), // BIT STRING + 0x00 // no unused bits ) } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KexAlgorithm.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KexAlgorithm.kt index 76597f7..5fd8872 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KexAlgorithm.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KexAlgorithm.kt @@ -57,18 +57,20 @@ internal interface KexAlgorithm { serverHostKey: ByteArray, clientPublicKey: ByteArray, serverPublicKey: ByteArray, - sharedSecret: ByteArray + sharedSecret: ByteArray, ): ByteArray { val transcript = ByteArrayOutputStream() fun writeString(data: ByteArray) { val len = data.size - transcript.write(byteArrayOf( - (len shr 24).toByte(), - (len shr 16).toByte(), - (len shr 8).toByte(), - len.toByte() - )) + transcript.write( + byteArrayOf( + (len shr 24).toByte(), + (len shr 16).toByte(), + (len shr 8).toByte(), + len.toByte() + ) + ) transcript.write(data) } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyDecryption.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyDecryption.kt index 8fe9f39..19fb5e6 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyDecryption.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyDecryption.kt @@ -30,7 +30,7 @@ internal object KeyDecryption { password: ByteArray, salt: ByteArray, rounds: Int, - cipherName: String + cipherName: String, ): ByteArray { val (jcaCipher, keySize, ivSize) = when (cipherName.lowercase()) { "aes256-ctr" -> Triple("AES/CTR/NoPadding", 32, 16) @@ -57,7 +57,7 @@ internal object KeyDecryption { data: ByteArray, password: ByteArray, salt: ByteArray, - cipherName: String + cipherName: String, ): ByteArray { val (jcaCipher, keySize) = when (cipherName.uppercase()) { "DES-EDE3-CBC" -> "DESede/CBC/NoPadding" to 24 @@ -98,7 +98,7 @@ internal object KeyDecryption { internal fun generateKeyFromPasswordSaltWithMD5( password: ByteArray, salt: ByteArray, - keyLen: Int + keyLen: Int, ): ByteArray { val md5 = MessageDigest.getInstance("MD5") val key = ByteArray(keyLen) diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyDerivation.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyDerivation.kt index 045a506..a2d67a9 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyDerivation.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyDerivation.kt @@ -31,7 +31,7 @@ internal class KeyDerivation( private val sharedSecret: ByteArray, private val exchangeHash: ByteArray, private val sessionId: ByteArray, - private val hashAlgorithm: String = "SHA-1" + private val hashAlgorithm: String = "SHA-1", ) { /** * Derived encryption/MAC keys for both directions. @@ -42,7 +42,7 @@ internal class KeyDerivation( val encryptionKeyClientToServer: ByteArray, val encryptionKeyServerToClient: ByteArray, val integrityKeyClientToServer: ByteArray, - val integrityKeyServerToClient: ByteArray + val integrityKeyServerToClient: ByteArray, ) /** @@ -53,16 +53,14 @@ internal class KeyDerivation( * @param macKeyLength Required MAC key length in bytes * @return Derived keys */ - fun deriveKeys(ivLength: Int, keyLength: Int, macKeyLength: Int): Keys { - return Keys( - initialIvClientToServer = deriveKey('A', ivLength), - initialIvServerToClient = deriveKey('B', ivLength), - encryptionKeyClientToServer = deriveKey('C', keyLength), - encryptionKeyServerToClient = deriveKey('D', keyLength), - integrityKeyClientToServer = deriveKey('E', macKeyLength), - integrityKeyServerToClient = deriveKey('F', macKeyLength) - ) - } + fun deriveKeys(ivLength: Int, keyLength: Int, macKeyLength: Int): Keys = Keys( + initialIvClientToServer = deriveKey('A', ivLength), + initialIvServerToClient = deriveKey('B', ivLength), + encryptionKeyClientToServer = deriveKey('C', keyLength), + encryptionKeyServerToClient = deriveKey('D', keyLength), + integrityKeyClientToServer = deriveKey('E', macKeyLength), + integrityKeyServerToClient = deriveKey('F', macKeyLength) + ) /** * Derive a single key according to RFC 4253 section 7.2. diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyEncryption.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyEncryption.kt index 398c18d..6cc8559 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyEncryption.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyEncryption.kt @@ -28,7 +28,7 @@ internal object KeyEncryption { data: ByteArray, password: ByteArray, salt: ByteArray, - cipherName: String + cipherName: String, ): ByteArray { val (jcaCipher, keySize) = when (cipherName.uppercase()) { "DES-EDE3-CBC" -> "DESede/CBC/NoPadding" to 24 @@ -62,7 +62,7 @@ internal object KeyEncryption { password: ByteArray, salt: ByteArray, rounds: Int, - cipherName: String + cipherName: String, ): ByteArray { val (jcaCipher, keySize, ivSize) = when (cipherName.lowercase()) { "aes256-ctr" -> Triple("AES/CTR/NoPadding", 32, 16) @@ -93,7 +93,5 @@ internal object KeyEncryption { return padded } - fun byteArrayToHex(bytes: ByteArray): String { - return bytes.joinToString("") { "%02X".format(it) } - } + fun byteArrayToHex(bytes: ByteArray): String = bytes.joinToString("") { "%02X".format(it) } } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyTypes.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyTypes.kt index e9e7995..174b185 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyTypes.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyTypes.kt @@ -21,25 +21,28 @@ import java.security.KeyPair import java.security.PublicKey import java.security.interfaces.ECPublicKey -internal fun inferKeyType(publicKey: PublicKey): String { - return when (publicKey.algorithm) { - "Ed25519" -> "ssh-ed25519" - "Ed448" -> "ssh-ed448" - "EdDSA" -> { - if (publicKey.encoded.size <= 44) "ssh-ed25519" else "ssh-ed448" - } - "EC", "ECDSA" -> { - val ecPub = publicKey as ECPublicKey - when (ecPub.params.order.bitLength()) { - 256 -> "ecdsa-sha2-nistp256" - 384 -> "ecdsa-sha2-nistp384" - 521 -> "ecdsa-sha2-nistp521" - else -> throw SshException("Unsupported EC curve with order bit length: ${ecPub.params.order.bitLength()}") - } +internal fun inferKeyType(publicKey: PublicKey): String = when (publicKey.algorithm) { + "Ed25519" -> "ssh-ed25519" + + "Ed448" -> "ssh-ed448" + + "EdDSA" -> { + if (publicKey.encoded.size <= 44) "ssh-ed25519" else "ssh-ed448" + } + + "EC", "ECDSA" -> { + val ecPub = publicKey as ECPublicKey + when (ecPub.params.order.bitLength()) { + 256 -> "ecdsa-sha2-nistp256" + 384 -> "ecdsa-sha2-nistp384" + 521 -> "ecdsa-sha2-nistp521" + else -> throw SshException("Unsupported EC curve with order bit length: ${ecPub.params.order.bitLength()}") } - "RSA" -> "ssh-rsa" - else -> throw SshException("Unsupported key type: ${publicKey.algorithm}") } + + "RSA" -> "ssh-rsa" + + else -> throw SshException("Unsupported key type: ${publicKey.algorithm}") } internal fun inferKeyType(keyPair: KeyPair): String = inferKeyType(keyPair.public) diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/MlKemHybridKeyExchange.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/MlKemHybridKeyExchange.kt index 04991f0..8d92c88 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/MlKemHybridKeyExchange.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/MlKemHybridKeyExchange.kt @@ -31,7 +31,7 @@ import java.security.MessageDigest */ internal class MlKemHybridKeyExchange( private val mlKemProvider: MlKemProvider = MlKemProviderFactory.provider, - private val x25519Provider: X25519Provider = X25519ProviderFactory.provider + private val x25519Provider: X25519Provider = X25519ProviderFactory.provider, ) : KexAlgorithm { companion object { private const val MLKEM768_PUBLIC_KEY_SIZE = 1184 diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/OpenSshKeyReader.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/OpenSshKeyReader.kt index e654f38..ac95d12 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/OpenSshKeyReader.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/OpenSshKeyReader.kt @@ -17,13 +17,22 @@ package org.connectbot.sshlib.crypto import org.connectbot.sshlib.SshException -import org.connectbot.sshlib.protocol.* +import org.connectbot.sshlib.protocol.readByteString +import org.connectbot.sshlib.protocol.readMpintUnsigned +import org.connectbot.sshlib.protocol.readString import java.math.BigInteger import java.nio.ByteBuffer import java.security.AlgorithmParameters import java.security.KeyFactory import java.security.KeyPair -import java.security.spec.* +import java.security.spec.ECGenParameterSpec +import java.security.spec.ECParameterSpec +import java.security.spec.ECPrivateKeySpec +import java.security.spec.ECPublicKeySpec +import java.security.spec.PKCS8EncodedKeySpec +import java.security.spec.RSAPrivateCrtKeySpec +import java.security.spec.RSAPublicKeySpec +import java.security.spec.X509EncodedKeySpec internal object OpenSshKeyReader { diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/OpenSshKeyWriter.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/OpenSshKeyWriter.kt index af09879..e4a4a7e 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/OpenSshKeyWriter.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/OpenSshKeyWriter.kt @@ -67,7 +67,11 @@ internal object OpenSshKeyWriter { } val binaryData = buildBinaryData( - cipherName, kdfName, kdfOptions, publicKeyBlob, encryptedSection + cipherName, + kdfName, + kdfOptions, + publicKeyBlob, + encryptedSection ) return formatOutput(binaryData) @@ -78,7 +82,7 @@ internal object OpenSshKeyWriter { kdfName: String, kdfOptions: ByteArray, publicKeyBlob: ByteArray, - encryptedSection: ByteArray + encryptedSection: ByteArray, ): ByteArray { val out = ByteArrayOutputStream() out.write(OPENSSH_V1_MAGIC) @@ -144,6 +148,7 @@ internal object OpenSshKeyWriter { privKey is java.security.interfaces.EdECPrivateKey -> { privKey.bytes.orElseThrow { SshException("Cannot extract Ed25519 seed") } } + privKey.javaClass.name.contains("Ed25519PrivateKey") -> { // Our Ed25519PrivateKey or similar — extract from PKCS#8 encoding val encoded = privKey.encoded @@ -156,6 +161,7 @@ internal object OpenSshKeyWriter { innerReader.readOctetString() } } + else -> throw SshException("Cannot extract Ed25519 seed from ${privKey.javaClass}") } } @@ -194,14 +200,12 @@ internal object OpenSshKeyWriter { out.write(encodeMpint(rsaPriv.primeQ.toByteArray())) } - private fun encodeUint32(value: Int): ByteArray { - return byteArrayOf( - (value ushr 24).toByte(), - (value ushr 16).toByte(), - (value ushr 8).toByte(), - value.toByte() - ) - } + private fun encodeUint32(value: Int): ByteArray = byteArrayOf( + (value ushr 24).toByte(), + (value ushr 16).toByte(), + (value ushr 8).toByte(), + value.toByte() + ) private fun formatOutput(data: ByteArray): String { val sb = StringBuilder() diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PacketAead.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PacketAead.kt index 92d9c4a..8cf0b83 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PacketAead.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PacketAead.kt @@ -50,9 +50,7 @@ internal interface PacketAead { */ fun decrypt(packetLength: ByteArray, ciphertext: ByteArray, tag: ByteArray): ByteArray - fun encryptLength(sequenceNumber: Long, plainLength: ByteArray): ByteArray = - throw UnsupportedOperationException("This cipher does not encrypt the length field") + fun encryptLength(sequenceNumber: Long, plainLength: ByteArray): ByteArray = throw UnsupportedOperationException("This cipher does not encrypt the length field") - fun decryptLength(sequenceNumber: Long, encryptedLength: ByteArray): ByteArray = - throw UnsupportedOperationException("This cipher does not encrypt the length field") + fun decryptLength(sequenceNumber: Long, encryptedLength: ByteArray): ByteArray = throw UnsupportedOperationException("This cipher does not encrypt the length field") } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PemKeyReader.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PemKeyReader.kt index ea1dccf..38982e3 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PemKeyReader.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PemKeyReader.kt @@ -21,7 +21,15 @@ import java.math.BigInteger import java.security.AlgorithmParameters import java.security.KeyFactory import java.security.KeyPair -import java.security.spec.* +import java.security.spec.ECGenParameterSpec +import java.security.spec.ECParameterSpec +import java.security.spec.ECPrivateKeySpec +import java.security.spec.ECPublicKeySpec +import java.security.spec.InvalidKeySpecException +import java.security.spec.NamedParameterSpec +import java.security.spec.PKCS8EncodedKeySpec +import java.security.spec.RSAPrivateCrtKeySpec +import java.security.spec.RSAPublicKeySpec import java.util.Base64 internal object PemKeyReader { @@ -32,7 +40,7 @@ internal object PemKeyReader { val type: PemType, var data: ByteArray, var procType: List?, - var dekInfo: List? + var dekInfo: List?, ) fun isEncrypted(text: String): Boolean { @@ -70,10 +78,12 @@ internal object PemKeyReader { type = PemType.RSA endMarker = "-----END RSA PRIVATE KEY-----" } + line.startsWith("-----BEGIN EC PRIVATE KEY-----") -> { type = PemType.EC endMarker = "-----END EC PRIVATE KEY-----" } + line.startsWith("-----BEGIN PRIVATE KEY-----") -> { type = PemType.PKCS8 endMarker = "-----END PRIVATE KEY-----" @@ -280,6 +290,7 @@ internal object PemKeyReader { ) return@readSequence KeyFactory.getInstance("EC").generatePublic(pubKeySpec) } + else -> seq.skipTag() } } @@ -288,16 +299,17 @@ internal object PemKeyReader { } } - internal fun oidToCurve(oid: ByteArray): Pair { - return when { - oid.contentEquals(SECP256R1_OID) -> - ECGenParameterSpec("secp256r1") to "ecdsa-sha2-nistp256" - oid.contentEquals(SECP384R1_OID) -> - ECGenParameterSpec("secp384r1") to "ecdsa-sha2-nistp384" - oid.contentEquals(SECP521R1_OID) -> - ECGenParameterSpec("secp521r1") to "ecdsa-sha2-nistp521" - else -> throw SshException("Unknown EC curve OID") - } + internal fun oidToCurve(oid: ByteArray): Pair = when { + oid.contentEquals(SECP256R1_OID) -> + ECGenParameterSpec("secp256r1") to "ecdsa-sha2-nistp256" + + oid.contentEquals(SECP384R1_OID) -> + ECGenParameterSpec("secp384r1") to "ecdsa-sha2-nistp384" + + oid.contentEquals(SECP521R1_OID) -> + ECGenParameterSpec("secp521r1") to "ecdsa-sha2-nistp521" + + else -> throw SshException("Unknown EC curve OID") } internal val SECP256R1_OID = byteArrayOf(0x2a, 0x86.toByte(), 0x48, 0xce.toByte(), 0x3d, 0x03, 0x01, 0x07) diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PlatformX25519Provider.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PlatformX25519Provider.kt index 4b585f0..5d1948c 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PlatformX25519Provider.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PlatformX25519Provider.kt @@ -31,19 +31,19 @@ internal class PlatformX25519Provider : X25519Provider { private const val ALGORITHM = "X25519" private val PKCS8_PREFIX = byteArrayOf( - 0x30, 0x2e, // SEQUENCE (46 bytes) - 0x02, 0x01, 0x00, // INTEGER 0 (version) - 0x30, 0x05, // SEQUENCE (5 bytes) - 0x06, 0x03, 0x2b, 0x65, 0x6e, // OID 1.3.101.110 (X25519) - 0x04, 0x22, // OCTET STRING (34 bytes) - 0x04, 0x20 // OCTET STRING (32 bytes) + 0x30, 0x2e, // SEQUENCE (46 bytes) + 0x02, 0x01, 0x00, // INTEGER 0 (version) + 0x30, 0x05, // SEQUENCE (5 bytes) + 0x06, 0x03, 0x2b, 0x65, 0x6e, // OID 1.3.101.110 (X25519) + 0x04, 0x22, // OCTET STRING (34 bytes) + 0x04, 0x20 // OCTET STRING (32 bytes) ) private val X509_PREFIX = byteArrayOf( - 0x30, 0x2a, // SEQUENCE (42 bytes) - 0x30, 0x05, // SEQUENCE (5 bytes) - 0x06, 0x03, 0x2b, 0x65, 0x6e, // OID 1.3.101.110 (X25519) - 0x03, 0x21, 0x00 // BIT STRING (33 bytes, 0 unused bits) + 0x30, 0x2a, // SEQUENCE (42 bytes) + 0x30, 0x05, // SEQUENCE (5 bytes) + 0x06, 0x03, 0x2b, 0x65, 0x6e, // OID 1.3.101.110 (X25519) + 0x03, 0x21, 0x00 // BIT STRING (33 bytes, 0 unused bits) ) private val BASE_POINT = ByteArray(X25519Provider.KEY_SIZE).apply { this[0] = 9 } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Poly1305.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Poly1305.kt index eee896c..68f0015 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Poly1305.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Poly1305.kt @@ -26,9 +26,20 @@ internal class Poly1305 { private const val BLOCK_SIZE = 16 } - private var h0 = 0; private var h1 = 0; private var h2 = 0; private var h3 = 0; private var h4 = 0 - private var r0 = 0; private var r1 = 0; private var r2 = 0; private var r3 = 0; private var r4 = 0 - private var s0 = 0; private var s1 = 0; private var s2 = 0; private var s3 = 0 + private var h0 = 0 + private var h1 = 0 + private var h2 = 0 + private var h3 = 0 + private var h4 = 0 + private var r0 = 0 + private var r1 = 0 + private var r2 = 0 + private var r3 = 0 + private var r4 = 0 + private var s0 = 0 + private var s1 = 0 + private var s2 = 0 + private var s3 = 0 private var buffer = ByteArray(BLOCK_SIZE) private var bufferPos = 0 @@ -53,7 +64,11 @@ internal class Poly1305 { s2 = readIntLE(key, 24) s3 = readIntLE(key, 28) - h0 = 0; h1 = 0; h2 = 0; h3 = 0; h4 = 0 + h0 = 0 + h1 = 0 + h2 = 0 + h3 = 0 + h4 = 0 buffer = ByteArray(BLOCK_SIZE) bufferPos = 0 finished = false @@ -155,9 +170,20 @@ internal class Poly1305 { } fun reset() { - r0 = 0; r1 = 0; r2 = 0; r3 = 0; r4 = 0 - s0 = 0; s1 = 0; s2 = 0; s3 = 0 - h0 = 0; h1 = 0; h2 = 0; h3 = 0; h4 = 0 + r0 = 0 + r1 = 0 + r2 = 0 + r3 = 0 + r4 = 0 + s0 = 0 + s1 = 0 + s2 = 0 + s3 = 0 + h0 = 0 + h1 = 0 + h2 = 0 + h3 = 0 + h4 = 0 buffer.fill(0) bufferPos = 0 } @@ -228,11 +254,10 @@ internal class Poly1305 { h0 = h0 and 0x03ffffff } - private fun readIntLE(buf: ByteArray, offset: Int): Int = - (buf[offset].toInt() and 0xff) or - ((buf[offset + 1].toInt() and 0xff) shl 8) or - ((buf[offset + 2].toInt() and 0xff) shl 16) or - ((buf[offset + 3].toInt() and 0xff) shl 24) + private fun readIntLE(buf: ByteArray, offset: Int): Int = (buf[offset].toInt() and 0xff) or + ((buf[offset + 1].toInt() and 0xff) shl 8) or + ((buf[offset + 2].toInt() and 0xff) shl 16) or + ((buf[offset + 3].toInt() and 0xff) shl 24) private fun writeIntLE(value: Int, buf: ByteArray, offset: Int) { buf[offset] = value.toByte() diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PrivateKeyReader.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PrivateKeyReader.kt index 403e6fa..ce7b56d 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PrivateKeyReader.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PrivateKeyReader.kt @@ -21,9 +21,7 @@ import java.util.Base64 internal object PrivateKeyReader { - fun read(keyData: ByteArray, passphrase: String? = null): SshPrivateKey { - return read(String(keyData, Charsets.UTF_8), passphrase) - } + fun read(keyData: ByteArray, passphrase: String? = null): SshPrivateKey = read(String(keyData, Charsets.UTF_8), passphrase) fun read(keyData: String, passphrase: String? = null): SshPrivateKey { val trimmed = keyData.trim() @@ -33,18 +31,18 @@ internal object PrivateKeyReader { val data = Base64.getDecoder().decode(base64) OpenSshKeyReader.read(data, passphrase) } + trimmed.startsWith("-----BEGIN RSA PRIVATE KEY-----") || - trimmed.startsWith("-----BEGIN EC PRIVATE KEY-----") || - trimmed.startsWith("-----BEGIN PRIVATE KEY-----") -> { + trimmed.startsWith("-----BEGIN EC PRIVATE KEY-----") || + trimmed.startsWith("-----BEGIN PRIVATE KEY-----") -> { PemKeyReader.read(trimmed, passphrase) } + else -> throw SshException("Unrecognized private key format") } } - fun isEncrypted(keyData: ByteArray): Boolean { - return isEncrypted(String(keyData, Charsets.UTF_8)) - } + fun isEncrypted(keyData: ByteArray): Boolean = isEncrypted(String(keyData, Charsets.UTF_8)) fun isEncrypted(keyData: String): Boolean { val trimmed = keyData.trim() @@ -54,11 +52,15 @@ internal object PrivateKeyReader { val data = Base64.getDecoder().decode(base64) OpenSshKeyReader.isEncrypted(data) } + trimmed.startsWith("-----BEGIN RSA PRIVATE KEY-----") || - trimmed.startsWith("-----BEGIN EC PRIVATE KEY-----") -> { + trimmed.startsWith("-----BEGIN EC PRIVATE KEY-----") -> { PemKeyReader.isEncrypted(trimmed) } - trimmed.startsWith("-----BEGIN PRIVATE KEY-----") -> false // PKCS#8 unencrypted + + trimmed.startsWith("-----BEGIN PRIVATE KEY-----") -> false + + // PKCS#8 unencrypted else -> throw SshException("Unrecognized private key format") } } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/RsaSignatureAlgorithm.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/RsaSignatureAlgorithm.kt index 338d691..3f2e35e 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/RsaSignatureAlgorithm.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/RsaSignatureAlgorithm.kt @@ -57,6 +57,6 @@ internal object RsaSignatureAlgorithm : SshSignatureAlgorithm { val sigBytes = signer.sign() return encodeSshString(algorithmName.toByteArray(Charsets.US_ASCII)) + - encodeSshString(sigBytes) + encodeSshString(sigBytes) } } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/SshPrivateKey.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/SshPrivateKey.kt index 89b3d65..b48af67 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/SshPrivateKey.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/SshPrivateKey.kt @@ -21,5 +21,5 @@ import java.security.KeyPair internal class SshPrivateKey( val keyType: String, val jcaKeyPair: KeyPair, - val signatureAlgorithm: String + val signatureAlgorithm: String, ) diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/SshPublicKeyEncoder.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/SshPublicKeyEncoder.kt index ae631f7..8fff51c 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/SshPublicKeyEncoder.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/SshPublicKeyEncoder.kt @@ -26,13 +26,11 @@ internal object SshPublicKeyEncoder { fun encode(keyPair: KeyPair, keyType: String): ByteArray = encode(keyPair.public, keyType) - fun encode(publicKey: PublicKey, keyType: String): ByteArray { - return when { - keyType == "ssh-ed25519" -> encodeEd25519(publicKey) - keyType.startsWith("ecdsa-sha2-") -> encodeEcdsa(publicKey as ECPublicKey, keyType) - keyType == "ssh-rsa" -> encodeRsa(publicKey as RSAPublicKey) - else -> throw SshException("Unsupported key type for encoding: $keyType") - } + fun encode(publicKey: PublicKey, keyType: String): ByteArray = when { + keyType == "ssh-ed25519" -> encodeEd25519(publicKey) + keyType.startsWith("ecdsa-sha2-") -> encodeEcdsa(publicKey as ECPublicKey, keyType) + keyType == "ssh-rsa" -> encodeRsa(publicKey as RSAPublicKey) + else -> throw SshException("Unsupported key type for encoding: $keyType") } private fun encodeEd25519(publicKey: PublicKey): ByteArray { @@ -40,7 +38,7 @@ internal object SshPublicKeyEncoder { val rawKey = pubKeyEncoded.copyOfRange(pubKeyEncoded.size - 32, pubKeyEncoded.size) return encodeSshString("ssh-ed25519".toByteArray(Charsets.US_ASCII)) + - encodeSshString(rawKey) + encodeSshString(rawKey) } private fun encodeEcdsa(ecPub: ECPublicKey, keyType: String): ByteArray { @@ -55,8 +53,8 @@ internal object SshPublicKeyEncoder { val qBytes = encodeEcPoint(ecPub, fieldSize) return encodeSshString(keyType.toByteArray(Charsets.US_ASCII)) + - encodeSshString(curveName.toByteArray(Charsets.US_ASCII)) + - encodeSshString(qBytes) + encodeSshString(curveName.toByteArray(Charsets.US_ASCII)) + + encodeSshString(qBytes) } internal fun encodeEcPoint(pubKey: ECPublicKey, fieldSize: Int): ByteArray { @@ -71,24 +69,22 @@ internal object SshPublicKeyEncoder { return result } - private fun padOrTrim(bytes: ByteArray, size: Int): ByteArray { - return when { - bytes.size == size -> bytes - bytes.size > size -> { - val start = bytes.size - size - bytes.copyOfRange(start, bytes.size) - } - else -> { - val result = ByteArray(size) - System.arraycopy(bytes, 0, result, size - bytes.size, bytes.size) - result - } + private fun padOrTrim(bytes: ByteArray, size: Int): ByteArray = when { + bytes.size == size -> bytes + + bytes.size > size -> { + val start = bytes.size - size + bytes.copyOfRange(start, bytes.size) } - } - private fun encodeRsa(rsaPub: RSAPublicKey): ByteArray { - return encodeSshString("ssh-rsa".toByteArray(Charsets.US_ASCII)) + - encodeMpint(rsaPub.publicExponent.toByteArray()) + - encodeMpint(rsaPub.modulus.toByteArray()) + else -> { + val result = ByteArray(size) + System.arraycopy(bytes, 0, result, size - bytes.size, bytes.size) + result + } } + + private fun encodeRsa(rsaPub: RSAPublicKey): ByteArray = encodeSshString("ssh-rsa".toByteArray(Charsets.US_ASCII)) + + encodeMpint(rsaPub.publicExponent.toByteArray()) + + encodeMpint(rsaPub.modulus.toByteArray()) } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/TinkX25519Provider.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/TinkX25519Provider.kt index a97e471..95a1de7 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/TinkX25519Provider.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/TinkX25519Provider.kt @@ -21,9 +21,7 @@ import com.google.crypto.tink.subtle.X25519 internal class TinkX25519Provider : X25519Provider { override fun generatePrivateKey(): ByteArray = X25519.generatePrivateKey() - override fun publicFromPrivate(privateKey: ByteArray): ByteArray = - X25519.publicFromPrivate(privateKey) + override fun publicFromPrivate(privateKey: ByteArray): ByteArray = X25519.publicFromPrivate(privateKey) - override fun computeSharedSecret(privateKey: ByteArray, publicKey: ByteArray): ByteArray = - X25519.computeSharedSecret(privateKey, publicKey) + override fun computeSharedSecret(privateKey: ByteArray, publicKey: ByteArray): ByteArray = X25519.computeSharedSecret(privateKey, publicKey) } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/TripleDesCbcCipher.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/TripleDesCbcCipher.kt index 0f37f6e..421b497 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/TripleDesCbcCipher.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/TripleDesCbcCipher.kt @@ -35,7 +35,7 @@ import javax.crypto.spec.SecretKeySpec internal class TripleDesCbcCipher( private val key: ByteArray, private val iv: ByteArray, - forEncryption: Boolean + forEncryption: Boolean, ) : PacketCipher { override val blockSize: Int = 8 @@ -55,11 +55,7 @@ internal class TripleDesCbcCipher( cipher.init(mode, keySpec, ivSpec) } - override fun encrypt(data: ByteArray): ByteArray { - return cipher.update(data) - } + override fun encrypt(data: ByteArray): ByteArray = cipher.update(data) - override fun decrypt(data: ByteArray): ByteArray { - return cipher.update(data) - } + override fun decrypt(data: ByteArray): ByteArray = cipher.update(data) } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/X25519ProviderFactory.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/X25519ProviderFactory.kt index 3129e5f..ed0d7c4 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/X25519ProviderFactory.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/X25519ProviderFactory.kt @@ -45,7 +45,5 @@ internal object X25519ProviderFactory { false } - private fun createTinkProvider(): X25519Provider { - return TinkX25519Provider() - } + private fun createTinkProvider(): X25519Provider = TinkX25519Provider() } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/ed25519/Ed25519KeyFactory.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/ed25519/Ed25519KeyFactory.kt index e16453e..5f3d292 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/ed25519/Ed25519KeyFactory.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/ed25519/Ed25519KeyFactory.kt @@ -41,9 +41,7 @@ internal class Ed25519KeyFactory : KeyFactorySpi() { throw InvalidKeySpecException("Unrecognized key spec: ${keySpec.javaClass}") } - override fun engineGetKeySpec(key: Key, keySpec: Class): T { - throw InvalidKeySpecException("Not implemented: $key $keySpec") - } + override fun engineGetKeySpec(key: Key, keySpec: Class): T = throw InvalidKeySpecException("Not implemented: $key $keySpec") override fun engineTranslateKey(key: Key): Key { if (key is Ed25519PublicKey || key is Ed25519PrivateKey) { diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/ed25519/Ed25519PrivateKey.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/ed25519/Ed25519PrivateKey.kt index 91532e2..56cce44 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/ed25519/Ed25519PrivateKey.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/ed25519/Ed25519PrivateKey.kt @@ -25,7 +25,9 @@ import java.security.spec.PKCS8EncodedKeySpec import javax.security.auth.DestroyFailedException import javax.security.auth.Destroyable -internal class Ed25519PrivateKey : PrivateKey, Destroyable { +internal class Ed25519PrivateKey : + PrivateKey, + Destroyable { private val seed: ByteArray private var destroyed = false diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/ed25519/Ed25519Provider.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/ed25519/Ed25519Provider.kt index 80d81d1..4e61a0e 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/ed25519/Ed25519Provider.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/ed25519/Ed25519Provider.kt @@ -25,10 +25,12 @@ import java.security.Security internal class Ed25519Provider : Provider(NAME, 1.0, "ConnectBot Ed25519 JCA Provider") { init { @Suppress("DEPRECATION", "removal") - AccessController.doPrivileged(PrivilegedAction { - setup() - null - }) + AccessController.doPrivileged( + PrivilegedAction { + setup() + null + } + ) } private fun setup() { diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/protocol/SshBufUtils.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/protocol/SshBufUtils.kt index f754e2f..7a15697 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/protocol/SshBufUtils.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/protocol/SshBufUtils.kt @@ -64,12 +64,10 @@ internal fun ByteBuffer.readMpintUnsigned(): BigInteger { * Utility functions for creating Kaitai Structs for SSH protocol types. */ -internal fun createAsciiString(str: String): AsciiString { - return AsciiString().apply { - setLen(str.length.toLong()) - setValue(str) - _check() - } +internal fun createAsciiString(str: String): AsciiString = AsciiString().apply { + setLen(str.length.toLong()) + setValue(str) + _check() } internal fun createUtf8String(str: String): Utf8String { @@ -81,12 +79,10 @@ internal fun createUtf8String(str: String): Utf8String { } } -internal fun createByteString(data: ByteArray): ByteString { - return ByteString().apply { - setLenData(data.size.toLong()) - setData(data) - _check() - } +internal fun createByteString(data: ByteArray): ByteString = ByteString().apply { + setLenData(data.size.toLong()) + setData(data) + _check() } internal fun createNameList(names: String): NameList { diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/protocol/SshClientStateMachine.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/protocol/SshClientStateMachine.kt index 9078f46..3030e66 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/protocol/SshClientStateMachine.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/protocol/SshClientStateMachine.kt @@ -17,7 +17,14 @@ package org.connectbot.sshlib.protocol import ru.nsk.kstatemachine.event.Event -import ru.nsk.kstatemachine.state.* +import ru.nsk.kstatemachine.state.activeStates +import ru.nsk.kstatemachine.state.finalState +import ru.nsk.kstatemachine.state.initialState +import ru.nsk.kstatemachine.state.invoke +import ru.nsk.kstatemachine.state.onEntry +import ru.nsk.kstatemachine.state.onExit +import ru.nsk.kstatemachine.state.state +import ru.nsk.kstatemachine.state.transition import ru.nsk.kstatemachine.statemachine.StateMachine import ru.nsk.kstatemachine.statemachine.createStdLibStateMachine import ru.nsk.kstatemachine.statemachine.processEventBlocking @@ -44,7 +51,7 @@ import ru.nsk.kstatemachine.transition.onTriggered * state management, a separate SshServerStateMachine would be needed. */ internal class SshClientStateMachine( - private val callbacks: SshClientCallbacks + private val callbacks: SshClientCallbacks, ) { sealed class SshEvent : Event { object Connect : SshEvent() @@ -65,7 +72,7 @@ internal class SshClientStateMachine( val channelType: String, val localChannelNumber: Int, val initialWindowSize: Int, - val maxPacketSize: Int + val maxPacketSize: Int, ) : SshEvent() data class ReceiveChannelOpenConfirmation(val msg: SshMsgChannelOpenConfirmation) : SshEvent() data class ReceiveChannelOpenFailure(val msg: SshMsgChannelOpenFailure) : SshEvent() @@ -73,7 +80,7 @@ internal class SshClientStateMachine( val recipientChannel: Int, val requestType: String, val wantReply: Boolean, - val message: SshMsgChannelRequest + val message: SshMsgChannelRequest, ) : SshEvent() object ReceiveChannelSuccess : SshEvent() object ReceiveChannelFailure : SshEvent() @@ -319,9 +326,7 @@ internal class SshClientStateMachine( val currentState: String get() = stateMachine.activeStates().firstOrNull()?.name ?: "Unknown" - fun isInState(stateName: String): Boolean { - return stateMachine.activeStates().any { it.name == stateName } - } + fun isInState(stateName: String): Boolean = stateMachine.activeStates().any { it.name == stateName } } internal interface SshClientCallbacks { diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/ForwardingChannelTransport.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/ForwardingChannelTransport.kt index 6811b67..66bb60d 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/ForwardingChannelTransport.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/ForwardingChannelTransport.kt @@ -27,7 +27,7 @@ import org.connectbot.sshlib.client.ForwardingChannel * (jump host / ProxyJump) without transiting the kernel network stack. */ internal class ForwardingChannelTransport( - private val channel: ForwardingChannel + private val channel: ForwardingChannel, ) : Transport { private var buffer = ByteArray(0) diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/KtorTcpTransport.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/KtorTcpTransport.kt index fdf869e..89a74e0 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/KtorTcpTransport.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/KtorTcpTransport.kt @@ -16,12 +16,21 @@ package org.connectbot.sshlib.transport -import io.ktor.network.selector.* -import io.ktor.network.sockets.* -import io.ktor.utils.io.* -import kotlinx.coroutines.* +import io.ktor.network.selector.SelectorManager +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.ByteWriteChannel +import io.ktor.utils.io.cancel +import io.ktor.utils.io.readFully +import io.ktor.utils.io.writeFully +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ClosedReceiveChannelException +import kotlinx.coroutines.delay +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope import java.net.Inet4Address import java.net.Inet6Address import java.net.InetAddress @@ -41,7 +50,7 @@ class KtorTcpTransport internal constructor( private val port: Int, private val addressResolver: AddressResolver, private val socketFactory: TcpSocketFactory? = null, - private val ipVersion: IpVersion = IpVersion.AUTO + private val ipVersion: IpVersion = IpVersion.AUTO, ) : Transport { constructor(host: String, port: Int = 22, ipVersion: IpVersion = IpVersion.AUTO) : this( @@ -86,20 +95,22 @@ class KtorTcpTransport internal constructor( } val addresses = when (ipVersion) { - IpVersion.IPv4_ONLY -> { + IpVersion.IPV4_ONLY -> { val filtered = allAddresses.filterIsInstance() if (filtered.isEmpty()) { throw TransportException("No IPv4 addresses found for host: $host") } filtered } - IpVersion.IPv6_ONLY -> { + + IpVersion.IPV6_ONLY -> { val filtered = allAddresses.filterIsInstance() if (filtered.isEmpty()) { throw TransportException("No IPv6 addresses found for host: $host") } filtered } + IpVersion.AUTO -> { // Happy Eyeballs (RFC 8305): interleave IPv6 and IPv4 val ipv6 = allAddresses.filterIsInstance() @@ -136,7 +147,7 @@ class KtorTcpTransport internal constructor( @OptIn(ExperimentalCoroutinesApi::class) private suspend fun connectHappyEyeballs( factory: TcpSocketFactory, - addresses: List + addresses: List, ): TransportSocket = supervisorScope { val resultChannel = Channel(capacity = 1) val failures = Collections.synchronizedList(mutableListOf()) diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/PacketIO.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/PacketIO.kt index 9d0c818..f6811dd 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/PacketIO.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/PacketIO.kt @@ -21,7 +21,8 @@ import org.connectbot.sshlib.crypto.PacketAead import org.connectbot.sshlib.crypto.PacketCipher import org.connectbot.sshlib.crypto.PacketCompressor import org.connectbot.sshlib.crypto.PacketMac -import org.connectbot.sshlib.protocol.* +import org.connectbot.sshlib.protocol.IdBanner +import org.connectbot.sshlib.protocol.UnencryptedPacket import org.slf4j.LoggerFactory import java.io.ByteArrayOutputStream import java.nio.ByteBuffer @@ -90,7 +91,7 @@ internal class PacketIO(private val transport: Transport) { serverToClientCipher: PacketCipher, serverToClientMac: PacketMac, clientToServerEtm: Boolean = false, - serverToClientEtm: Boolean = false + serverToClientEtm: Boolean = false, ) { this.sendCipher = clientToServerCipher this.sendMac = clientToServerMac @@ -108,7 +109,7 @@ internal class PacketIO(private val transport: Transport) { */ fun enableAead( clientToServerAead: PacketAead, - serverToClientAead: PacketAead + serverToClientAead: PacketAead, ) { this.sendAead = clientToServerAead this.receiveAead = serverToClientAead @@ -141,7 +142,7 @@ internal class PacketIO(private val transport: Transport) { fun enableCompression( clientToServer: PacketCompressor?, serverToClient: PacketCompressor?, - immediateActivation: Boolean + immediateActivation: Boolean, ) { this.sendCompressor = clientToServer this.receiveCompressor = serverToClient @@ -448,7 +449,7 @@ internal class PacketIO(private val transport: Transport) { messageType: Int, payload: ByteArray, cipher: PacketCipher, - mac: PacketMac + mac: PacketMac, ) { val payloadLength = 1 + payload.size val blockSize = cipher.blockSize @@ -499,7 +500,7 @@ internal class PacketIO(private val transport: Transport) { messageType: Int, payload: ByteArray, cipher: PacketCipher, - mac: PacketMac + mac: PacketMac, ) { val payloadLength = 1 + payload.size val blockSize = cipher.blockSize @@ -544,7 +545,7 @@ internal class PacketIO(private val transport: Transport) { private suspend fun writeAeadPacket( messageType: Int, payload: ByteArray, - aead: PacketAead + aead: PacketAead, ) { val payloadLength = 1 + payload.size // message type + payload val blockSize = if (aead.encryptsLength) 8 else 16 @@ -634,7 +635,8 @@ internal class PacketIO(private val transport: Transport) { if (bannerBytes.size() >= 2) { val bytes = bannerBytes.toByteArray() if (bytes[bytes.size - 2] == '\r'.code.toByte() && - bytes[bytes.size - 1] == '\n'.code.toByte()) { + bytes[bytes.size - 1] == '\n'.code.toByte() + ) { break } } diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/TransportDependencies.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/TransportDependencies.kt index ca216ff..f1f1971 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/TransportDependencies.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/TransportDependencies.kt @@ -16,14 +16,18 @@ package org.connectbot.sshlib.transport -import java.net.InetAddress -import io.ktor.network.sockets.* import io.ktor.network.selector.SelectorManager +import io.ktor.network.sockets.InetSocketAddress +import io.ktor.network.sockets.Socket +import io.ktor.network.sockets.isClosed +import io.ktor.network.sockets.openReadChannel +import io.ktor.network.sockets.openWriteChannel import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.ByteWriteChannel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.Closeable +import java.net.InetAddress /** * Resolves hostnames to IP addresses. @@ -49,15 +53,13 @@ interface TcpSocketFactory { } internal class DefaultAddressResolver : AddressResolver { - override suspend fun resolve(host: String): List { - return withContext(Dispatchers.IO) { - InetAddress.getAllByName(host).toList() - } + override suspend fun resolve(host: String): List = withContext(Dispatchers.IO) { + InetAddress.getAllByName(host).toList() } } internal class KtorTcpSocketFactory( - private val selectorManager: SelectorManager + private val selectorManager: SelectorManager, ) : TcpSocketFactory { override suspend fun connect(address: InetAddress, port: Int): TransportSocket { val socketAddress = InetSocketAddress(address.hostAddress, port) diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/TransportFactory.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/TransportFactory.kt index c656025..75a1189 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/TransportFactory.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/transport/TransportFactory.kt @@ -22,10 +22,12 @@ package org.connectbot.sshlib.transport enum class IpVersion { /** Use Happy Eyeballs (RFC 8305) to race IPv6 and IPv4. */ AUTO, + /** Connect only over IPv4. */ - IPv4_ONLY, + IPV4_ONLY, + /** Connect only over IPv6. */ - IPv6_ONLY + IPV6_ONLY, } /** @@ -67,7 +69,7 @@ fun interface TransportFactory { class KtorTcpTransportFactory( private val host: String, private val port: Int = 22, - private val ipVersion: IpVersion = IpVersion.AUTO + private val ipVersion: IpVersion = IpVersion.AUTO, ) : TransportFactory { override suspend fun create(): Transport { val transport = KtorTcpTransport(host, port, ipVersion = ipVersion) diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/AgentProtocolTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/AgentProtocolTest.kt index a395af2..57b2e06 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/AgentProtocolTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/AgentProtocolTest.kt @@ -20,8 +20,18 @@ import io.kaitai.struct.ByteBufferKaitaiStream import kotlinx.coroutines.test.runTest import org.connectbot.sshlib.client.AgentProtocolHandler import org.connectbot.sshlib.client.AgentSessionInfo -import org.connectbot.sshlib.protocol.* -import org.junit.Assert.* +import org.connectbot.sshlib.protocol.SshAgentIdentitiesAnswer +import org.connectbot.sshlib.protocol.SshAgentMessage +import org.connectbot.sshlib.protocol.SshAgentSignResponse +import org.connectbot.sshlib.protocol.SshAgentcRequestIdentities +import org.connectbot.sshlib.protocol.SshAgentcSignRequest +import org.connectbot.sshlib.protocol.createByteString +import org.connectbot.sshlib.protocol.toByteArray +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue import org.junit.Test import java.nio.ByteBuffer @@ -92,16 +102,12 @@ class AgentProtocolTest { @Test fun `handler returns identities answer`() = runTest { val testProvider = object : AgentProvider { - override suspend fun getIdentities(): List { - return listOf( - AgentIdentity(byteArrayOf(1, 2, 3), "key1"), - AgentIdentity(byteArrayOf(4, 5, 6), "key2") - ) - } + override suspend fun getIdentities(): List = listOf( + AgentIdentity(byteArrayOf(1, 2, 3), "key1"), + AgentIdentity(byteArrayOf(4, 5, 6), "key2") + ) - override suspend fun signData(context: AgentSigningContext): ByteArray? { - return null - } + override suspend fun signData(context: AgentSigningContext): ByteArray? = null } val sessionInfo = AgentSessionInfo( @@ -115,7 +121,7 @@ class AgentProtocolTest { val response = handler.handleRequest(requestMessage) val (messageType, payload) = parseAgentMessage(response) - assertEquals(12, messageType) // SSH_AGENT_IDENTITIES_ANSWER + assertEquals(12, messageType) // SSH_AGENT_IDENTITIES_ANSWER val stream = ByteBufferKaitaiStream(payload) val answer = SshAgentIdentitiesAnswer(stream) @@ -132,9 +138,7 @@ class AgentProtocolTest { val testProvider = object : AgentProvider { override suspend fun getIdentities(): List = emptyList() - override suspend fun signData(context: AgentSigningContext): ByteArray? { - return byteArrayOf(9, 8, 7, 6, 5) - } + override suspend fun signData(context: AgentSigningContext): ByteArray? = byteArrayOf(9, 8, 7, 6, 5) } val sessionInfo = AgentSessionInfo( @@ -156,7 +160,7 @@ class AgentProtocolTest { val response = handler.handleRequest(requestMessage) val (messageType, payload) = parseAgentMessage(response) - assertEquals(14, messageType) // SSH_AGENT_SIGN_RESPONSE + assertEquals(14, messageType) // SSH_AGENT_SIGN_RESPONSE val stream = ByteBufferKaitaiStream(payload) val signResponse = SshAgentSignResponse(stream) @@ -170,9 +174,7 @@ class AgentProtocolTest { val testProvider = object : AgentProvider { override suspend fun getIdentities(): List = emptyList() - override suspend fun signData(context: AgentSigningContext): ByteArray? { - return null - } + override suspend fun signData(context: AgentSigningContext): ByteArray? = null } val sessionInfo = AgentSessionInfo( @@ -194,7 +196,7 @@ class AgentProtocolTest { val response = handler.handleRequest(requestMessage) val (messageType, _) = parseAgentMessage(response) - assertEquals(5, messageType) // SSH_AGENT_FAILURE + assertEquals(5, messageType) // SSH_AGENT_FAILURE } @Test diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/AgentProviderTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/AgentProviderTest.kt index 3b60d1c..3f771d9 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/AgentProviderTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/AgentProviderTest.kt @@ -17,7 +17,10 @@ package org.connectbot.sshlib import kotlinx.coroutines.test.runTest -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull import org.junit.Test class AgentProviderTest { @@ -77,25 +80,21 @@ class AgentProviderTest { @Test fun `AgentProvider can be implemented with custom logic`() = runTest { val testProvider = object : AgentProvider { - override suspend fun getIdentities(): List { - return listOf( - AgentIdentity( - publicKeyBlob = byteArrayOf(1, 2, 3), - comment = "test-key-1" - ), - AgentIdentity( - publicKeyBlob = byteArrayOf(4, 5, 6), - comment = "test-key-2" - ) + override suspend fun getIdentities(): List = listOf( + AgentIdentity( + publicKeyBlob = byteArrayOf(1, 2, 3), + comment = "test-key-1" + ), + AgentIdentity( + publicKeyBlob = byteArrayOf(4, 5, 6), + comment = "test-key-2" ) - } + ) - override suspend fun signData(context: AgentSigningContext): ByteArray? { - return if (context.isBound) { - byteArrayOf(1, 2, 3, 4) - } else { - null - } + override suspend fun signData(context: AgentSigningContext): ByteArray? = if (context.isBound) { + byteArrayOf(1, 2, 3, 4) + } else { + null } } diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/SshKeysTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/SshKeysTest.kt index 4752eea..2b7be68 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/SshKeysTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/SshKeysTest.kt @@ -23,10 +23,8 @@ import kotlin.test.assertTrue class SshKeysTest { - private fun readKey(resourcePath: String): String { - return javaClass.getResourceAsStream("/keys/$resourcePath")!! - .bufferedReader().readText() - } + private fun readKey(resourcePath: String): String = javaClass.getResourceAsStream("/keys/$resourcePath")!! + .bufferedReader().readText() @Test fun `decodePemPrivateKey Ed25519 OpenSSH format`() { diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/SshSigningTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/SshSigningTest.kt index 39aba50..959d7fe 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/SshSigningTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/SshSigningTest.kt @@ -32,10 +32,8 @@ import kotlin.test.assertTrue class SshSigningTest { - private fun readKey(resourcePath: String): String { - return javaClass.getResourceAsStream("/keys/$resourcePath")!! - .bufferedReader().readText() - } + private fun readKey(resourcePath: String): String = javaClass.getResourceAsStream("/keys/$resourcePath")!! + .bufferedReader().readText() private fun signAndVerify(algorithmName: String, keyResource: String, passphrase: String? = null) { val keyData = readKey(keyResource) diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/ForwardingChannelTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/ForwardingChannelTest.kt index 852d277..8b008c5 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/ForwardingChannelTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/ForwardingChannelTest.kt @@ -19,9 +19,11 @@ package org.connectbot.sshlib.client import io.mockk.coEvery import io.mockk.coVerify import io.mockk.mockk -import io.mockk.slot import kotlinx.coroutines.test.runTest -import org.junit.Assert.* +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Test class ForwardingChannelTest { @@ -30,7 +32,7 @@ class ForwardingChannelTest { connection: SshConnection = mockk(relaxed = true), remoteWindowSize: Long = 64 * 1024, maxPacketSize: Int = 32 * 1024, - initialWindowSize: Int = 256 * 1024 + initialWindowSize: Int = 256 * 1024, ): Pair { val channel = ForwardingChannel( connection = connection, diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/PortForwardingIntegrationTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/PortForwardingIntegrationTest.kt index 0049198..2a9e96b 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/PortForwardingIntegrationTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/PortForwardingIntegrationTest.kt @@ -23,7 +23,10 @@ import org.connectbot.sshlib.PublicKey import org.connectbot.sshlib.Socks5Authenticator import org.connectbot.sshlib.SshClient import org.connectbot.sshlib.SshClientConfig -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.Timeout import org.slf4j.LoggerFactory @@ -158,8 +161,10 @@ class PortForwardingIntegrationTest { try { val forwarder = client.remotePortForward( - "127.0.0.1", 0, - "127.0.0.1", localPort + "127.0.0.1", + 0, + "127.0.0.1", + localPort ) assertNotNull(forwarder, "Should create remote port forwarder") assertTrue(forwarder!!.isActive, "Forwarder should be active") @@ -213,12 +218,14 @@ class PortForwardingIntegrationTest { assertEquals(0x00.toByte(), methodReply[1]) // SOCKS5 CONNECT to 127.0.0.1:22 (the SSH server) - out.write(byteArrayOf( - 0x05, 0x01, 0x00, // version, CMD_CONNECT, RSV - 0x01, // ATYP IPv4 - 127, 0, 0, 1, // 127.0.0.1 - 0x00, 0x16 // port 22 - )) + out.write( + byteArrayOf( + 0x05, 0x01, 0x00, // version, CMD_CONNECT, RSV + 0x01, // ATYP IPv4 + 127, 0, 0, 1, // 127.0.0.1 + 0x00, 0x16 // port 22 + ) + ) out.flush() // Read connect reply (10 bytes: ver, rep, rsv, atyp, 4 addr, 2 port) @@ -292,9 +299,7 @@ class PortForwardingIntegrationTest { try { val authenticator = object : Socks5Authenticator { - override fun authenticate(username: String, password: String): Boolean { - return username == "socks_user" && password == "socks_pass" - } + override fun authenticate(username: String, password: String): Boolean = username == "socks_user" && password == "socks_pass" } val forwarder = client.dynamicPortForward( diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/SessionChannelTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/SessionChannelTest.kt index 439b3e2..4da6f33 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/SessionChannelTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/SessionChannelTest.kt @@ -17,12 +17,12 @@ package org.connectbot.sshlib.client import io.mockk.coEvery -import io.mockk.coVerify import io.mockk.mockk import io.mockk.slot import kotlinx.coroutines.test.runTest -import org.connectbot.sshlib.protocol.SshMsgChannelRequest -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Test class SessionChannelTest { @@ -35,7 +35,7 @@ class SessionChannelTest { localChannelNumber = 0, _remoteChannelNumber = 1, maxPacketSize = 32 * 1024, - remoteWindowSize = 64 * 1024L, + remoteWindowSize = 64 * 1024L ) return channel to connection } @@ -55,7 +55,7 @@ class SessionChannelTest { widthChars = 120, heightRows = 40, widthPixels = 960, - heightPixels = 640, + heightPixels = 640 ) assertTrue(result) @@ -74,7 +74,7 @@ class SessionChannelTest { widthChars = 80, heightRows = 24, widthPixels = 0, - heightPixels = 0, + heightPixels = 0 ) assertFalse(result) diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/Socks5HandlerTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/Socks5HandlerTest.kt index 090d146..a266ff0 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/Socks5HandlerTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/Socks5HandlerTest.kt @@ -16,10 +16,14 @@ package org.connectbot.sshlib.client -import io.ktor.utils.io.* +import io.ktor.utils.io.ByteChannel +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.readFully import kotlinx.coroutines.test.runTest import org.connectbot.sshlib.Socks5Authenticator -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull import org.junit.Test import java.nio.ByteBuffer @@ -29,7 +33,7 @@ class Socks5HandlerTest { host: String, port: Int, authMethods: ByteArray = byteArrayOf(0x00), - useDomain: Boolean = true + useDomain: Boolean = true, ): ByteArray { val buf = ByteBuffer.allocate(512) @@ -62,7 +66,7 @@ class Socks5HandlerTest { private fun buildSocks5ConnectIPv4( addr: ByteArray, port: Int, - authMethods: ByteArray = byteArrayOf(0x00) + authMethods: ByteArray = byteArrayOf(0x00), ): ByteArray { val buf = ByteBuffer.allocate(512) @@ -89,7 +93,7 @@ class Socks5HandlerTest { private fun buildSocks5ConnectIPv6( addr: ByteArray, port: Int, - authMethods: ByteArray = byteArrayOf(0x00) + authMethods: ByteArray = byteArrayOf(0x00), ): ByteArray { val buf = ByteBuffer.allocate(512) @@ -117,7 +121,7 @@ class Socks5HandlerTest { host: String, port: Int, username: String, - password: String + password: String, ): ByteArray { val buf = ByteBuffer.allocate(512) @@ -212,9 +216,7 @@ class Socks5HandlerTest { @Test fun `username password auth succeeds`() = runTest { val authenticator = object : Socks5Authenticator { - override fun authenticate(username: String, password: String): Boolean { - return username == "user" && password == "pass" - } + override fun authenticate(username: String, password: String): Boolean = username == "user" && password == "pass" } val handler = Socks5Handler(authenticator) val input = buildSocks5WithAuth("example.com", 80, "user", "pass") diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/SshClientIntegrationTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/SshClientIntegrationTest.kt index 3c3f949..70566c9 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/SshClientIntegrationTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/SshClientIntegrationTest.kt @@ -26,11 +26,15 @@ import org.connectbot.sshlib.AuthPublicKey import org.connectbot.sshlib.HostKeyVerifier import org.connectbot.sshlib.KeyboardInteractiveCallback import org.connectbot.sshlib.PublicKey -import org.connectbot.sshlib.SshClientConfig import org.connectbot.sshlib.SshClient +import org.connectbot.sshlib.SshClientConfig import org.connectbot.sshlib.SshSigning import org.connectbot.sshlib.blocking.BlockingSshClient -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Assertions.fail import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource @@ -78,7 +82,7 @@ class SshClientIntegrationTest { "aes256-ctr", "aes128-cbc", "aes256-cbc", - "3des-cbc", + "3des-cbc" ) @JvmStatic @@ -92,13 +96,13 @@ class SshClientIntegrationTest { "diffie-hellman-group14-sha256", "diffie-hellman-group14-sha1", "diffie-hellman-group-exchange-sha1", - "diffie-hellman-group1-sha1", + "diffie-hellman-group1-sha1" ) @JvmStatic fun hostKeyAlgorithms() = listOf( "rsa-sha2-256", - "rsa-sha2-512", + "rsa-sha2-512" ) @JvmStatic @@ -106,7 +110,7 @@ class SshClientIntegrationTest { "hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com", "hmac-sha2-256", - "hmac-sha2-512", + "hmac-sha2-512" ) } @@ -367,7 +371,7 @@ class SshClientIntegrationTest { name: String, instruction: String, prompts: List, - respond: suspend (responses: List) -> Unit + respond: suspend (responses: List) -> Unit, ) { respond(prompts.map { PASSWORD }) } @@ -396,7 +400,7 @@ class SshClientIntegrationTest { name: String, instruction: String, prompts: List, - respond: suspend (responses: List) -> Unit + respond: suspend (responses: List) -> Unit, ) { respond(prompts.map { "wrongpassword" }) } @@ -412,10 +416,8 @@ class SshClientIntegrationTest { // --- AuthHandler integration tests --- - private fun readTestKey(): String { - return javaClass.getResourceAsStream("/openssh-server/test_ed25519")!! - .bufferedReader().readText() - } + private fun readTestKey(): String = javaClass.getResourceAsStream("/openssh-server/test_ed25519")!! + .bufferedReader().readText() @Test fun `auth handler should authenticate with password fallback`() { @@ -439,8 +441,9 @@ class SshClientIntegrationTest { override suspend fun onSignatureRequest(key: AuthPublicKey, dataToSign: ByteArray): ByteArray? = null override suspend fun onKeyboardInteractivePrompt( - name: String, instruction: String, - prompts: List + name: String, + instruction: String, + prompts: List, ): List? = null override suspend fun onPasswordNeeded(): String = PASSWORD @@ -472,8 +475,9 @@ class SshClientIntegrationTest { override suspend fun onSignatureRequest(key: AuthPublicKey, dataToSign: ByteArray): ByteArray? = null override suspend fun onKeyboardInteractivePrompt( - name: String, instruction: String, - prompts: List + name: String, + instruction: String, + prompts: List, ): List = prompts.map { PASSWORD } override suspend fun onPasswordNeeded(): String? = null @@ -503,13 +507,12 @@ class SshClientIntegrationTest { val handler = object : AuthHandler { override suspend fun onPublicKeysNeeded(): List = listOf(pubKey) - override suspend fun onSignatureRequest(key: AuthPublicKey, dataToSign: ByteArray): ByteArray { - return SshSigning.sign(key.algorithmName, keyData, null, dataToSign) - } + override suspend fun onSignatureRequest(key: AuthPublicKey, dataToSign: ByteArray): ByteArray = SshSigning.sign(key.algorithmName, keyData, null, dataToSign) override suspend fun onKeyboardInteractivePrompt( - name: String, instruction: String, - prompts: List + name: String, + instruction: String, + prompts: List, ): List? = null override suspend fun onPasswordNeeded(): String? = null @@ -552,8 +555,9 @@ class SshClientIntegrationTest { } override suspend fun onKeyboardInteractivePrompt( - name: String, instruction: String, - prompts: List + name: String, + instruction: String, + prompts: List, ): List? { callSequence.add("kbd") // Skip keyboard-interactive @@ -599,8 +603,9 @@ class SshClientIntegrationTest { override suspend fun onSignatureRequest(key: AuthPublicKey, dataToSign: ByteArray): ByteArray? = null override suspend fun onKeyboardInteractivePrompt( - name: String, instruction: String, - prompts: List + name: String, + instruction: String, + prompts: List, ): List? = null override suspend fun onPasswordNeeded(): String = PASSWORD diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/StreamForwarderTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/StreamForwarderTest.kt index e68239d..18d5f64 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/StreamForwarderTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/StreamForwarderTest.kt @@ -16,15 +16,17 @@ package org.connectbot.sshlib.client -import io.ktor.utils.io.* +import io.ktor.utils.io.ByteChannel +import io.ktor.utils.io.writeFully import io.mockk.coEvery import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test class StreamForwarderTest { @@ -45,7 +47,11 @@ class StreamForwarderTest { val output = ByteChannel(autoFlush = true) val forwarder = StreamForwarder.create( - connection, input, output, "example.com", 80 + connection, + input, + output, + "example.com", + 80 ) assertNotNull(forwarder) assertTrue(forwarder!!.isActive) @@ -67,7 +73,11 @@ class StreamForwarderTest { val output = ByteChannel(autoFlush = true) val forwarder = StreamForwarder.create( - connection, input, output, "example.com", 80 + connection, + input, + output, + "example.com", + 80 ) assertNull(forwarder) } @@ -82,7 +92,11 @@ class StreamForwarderTest { val output = ByteChannel(autoFlush = true) val forwarder = StreamForwarder.create( - connection, input, output, "example.com", 80 + connection, + input, + output, + "example.com", + 80 )!! forwarder.stop() diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/AesGcmCipherTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/AesGcmCipherTest.kt index cfd80ea..24ac6c9 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/AesGcmCipherTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/AesGcmCipherTest.kt @@ -31,8 +31,7 @@ class AesGcmCipherTest { return iv } - private fun packetLengthBytes(length: Int): ByteArray = - ByteBuffer.allocate(4).putInt(length).array() + private fun packetLengthBytes(length: Int): ByteArray = ByteBuffer.allocate(4).putInt(length).array() @Test fun roundTripAes128() { diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/AlgorithmsTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/AlgorithmsTest.kt index 81a33ea..d26698e 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/AlgorithmsTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/AlgorithmsTest.kt @@ -16,7 +16,12 @@ package org.connectbot.sshlib.crypto -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertSame +import org.junit.Assert.assertTrue import org.junit.Test class AlgorithmsTest { diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/ChaCha20Poly1305CipherTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/ChaCha20Poly1305CipherTest.kt index d5ec438..2315a7e 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/ChaCha20Poly1305CipherTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/ChaCha20Poly1305CipherTest.kt @@ -29,7 +29,7 @@ class ChaCha20Poly1305CipherTest { fun `Poly1305 RFC 8439 test vector`() { val key = hexToBytes( "85d6be7857556d337f4452fe42d506a8" + - "0103808afb0db2fd4abff6af4149f51b" + "0103808afb0db2fd4abff6af4149f51b" ) val message = "Cryptographic Forum Research Group".toByteArray(Charsets.US_ASCII) val expectedTag = hexToBytes("a8061dc1305136c6c22b8baf0c0127a9") @@ -47,7 +47,7 @@ class ChaCha20Poly1305CipherTest { fun `Poly1305 with multi-block input`() { val key = hexToBytes( "85d6be7857556d337f4452fe42d506a8" + - "0103808afb0db2fd4abff6af4149f51b" + "0103808afb0db2fd4abff6af4149f51b" ) val message = "Cryptographic Forum Research Group".toByteArray(Charsets.US_ASCII) @@ -200,6 +200,5 @@ class ChaCha20Poly1305CipherTest { } } - private fun hexToBytes(hex: String): ByteArray = - hex.chunked(2).map { it.toInt(16).toByte() }.toByteArray() + private fun hexToBytes(hex: String): ByteArray = hex.chunked(2).map { it.toInt(16).toByte() }.toByteArray() } diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/Curve25519KeyExchangeTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/Curve25519KeyExchangeTest.kt index 9b56914..f05dbe6 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/Curve25519KeyExchangeTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/Curve25519KeyExchangeTest.kt @@ -110,7 +110,5 @@ class Curve25519KeyExchangeTest { assertEquals("SHA-256", kex.hashAlgorithm) } - private fun hexToBytes(hex: String): ByteArray { - return hex.chunked(2).map { it.toInt(16).toByte() }.toByteArray() - } + private fun hexToBytes(hex: String): ByteArray = hex.chunked(2).map { it.toInt(16).toByte() }.toByteArray() } diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/DerTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/DerTest.kt index f68a8c2..3c01b00 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/DerTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/DerTest.kt @@ -33,7 +33,7 @@ class DerTest { integer(BigInteger.valueOf(20)) } } - + // Expected: // 0x30 (SEQUENCE) // 0x06 (Length: 2 + 1 + 1 + 2) @@ -44,7 +44,7 @@ class DerTest { // 0x01 (Length) // 0x14 (20) val expected = byteArrayOf(0x30, 0x06, 0x02, 0x01, 0x0A, 0x02, 0x01, 0x14) - + assertArrayEquals(expected, encoded) } @@ -62,7 +62,7 @@ class DerTest { val expected = byteArrayOf(0x30, 0x05, 0x30, 0x03, 0x02, 0x01, 0x01) assertArrayEquals(expected, encoded) } - + @Test fun testReader() { val data = byteArrayOf(0x30, 0x06, 0x02, 0x01, 0x0A, 0x02, 0x01, 0x14) diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/DiffieHellmanGroupExchangeTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/DiffieHellmanGroupExchangeTest.kt index 010c625..e16a213 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/DiffieHellmanGroupExchangeTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/DiffieHellmanGroupExchangeTest.kt @@ -127,9 +127,14 @@ class DiffieHellmanGroupExchangeTest { gex.setGroup(DhGroups.GROUP14_P, DhGroups.GENERATOR) val h = gex.computeExchangeHash( - "V_C".toByteArray(), "V_S".toByteArray(), - ByteArray(1), ByteArray(1), - ByteArray(1), ByteArray(1), ByteArray(1), ByteArray(1) + "V_C".toByteArray(), + "V_S".toByteArray(), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1) ) assertEquals(32, h.size) } @@ -140,9 +145,14 @@ class DiffieHellmanGroupExchangeTest { gex.setGroup(DhGroups.GROUP14_P, DhGroups.GENERATOR) val h = gex.computeExchangeHash( - "V_C".toByteArray(), "V_S".toByteArray(), - ByteArray(1), ByteArray(1), - ByteArray(1), ByteArray(1), ByteArray(1), ByteArray(1) + "V_C".toByteArray(), + "V_S".toByteArray(), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1) ) assertEquals(20, h.size) } diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/DiffieHellmanTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/DiffieHellmanTest.kt index 4352a39..b86357d 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/DiffieHellmanTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/DiffieHellmanTest.kt @@ -171,9 +171,14 @@ class DiffieHellmanTest { fun `exchange hash uses SHA-256`() { val dh = DiffieHellman("SHA-256", DhGroups.GROUP14_P, DhGroups.GENERATOR) val h = dh.computeExchangeHash( - "V_C".toByteArray(), "V_S".toByteArray(), - ByteArray(1), ByteArray(1), - ByteArray(1), ByteArray(1), ByteArray(1), ByteArray(1) + "V_C".toByteArray(), + "V_S".toByteArray(), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1) ) assertEquals(32, h.size) } @@ -182,9 +187,14 @@ class DiffieHellmanTest { fun `exchange hash uses SHA-512`() { val dh = DiffieHellman("SHA-512", DhGroups.GROUP16_P, DhGroups.GENERATOR) val h = dh.computeExchangeHash( - "V_C".toByteArray(), "V_S".toByteArray(), - ByteArray(1), ByteArray(1), - ByteArray(1), ByteArray(1), ByteArray(1), ByteArray(1) + "V_C".toByteArray(), + "V_S".toByteArray(), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1) ) assertEquals(64, h.size) } @@ -193,9 +203,14 @@ class DiffieHellmanTest { fun `exchange hash uses SHA-1`() { val dh = DiffieHellman("SHA-1", DhGroups.GROUP1_P, DhGroups.GENERATOR) val h = dh.computeExchangeHash( - "V_C".toByteArray(), "V_S".toByteArray(), - ByteArray(1), ByteArray(1), - ByteArray(1), ByteArray(1), ByteArray(1), ByteArray(1) + "V_C".toByteArray(), + "V_S".toByteArray(), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1) ) assertEquals(20, h.size) } diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/EcdhKeyExchangeTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/EcdhKeyExchangeTest.kt index 15efe45..bfaf962 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/EcdhKeyExchangeTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/EcdhKeyExchangeTest.kt @@ -155,9 +155,14 @@ class EcdhKeyExchangeTest { ecdh.generateClientKeys() val h = ecdh.computeExchangeHash( - "V_C".toByteArray(), "V_S".toByteArray(), - ByteArray(1), ByteArray(1), - ByteArray(1), ByteArray(1), ByteArray(1), ByteArray(1) + "V_C".toByteArray(), + "V_S".toByteArray(), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1) ) assertEquals(32, h.size) // SHA-256 output } @@ -168,9 +173,14 @@ class EcdhKeyExchangeTest { ecdh.generateClientKeys() val h = ecdh.computeExchangeHash( - "V_C".toByteArray(), "V_S".toByteArray(), - ByteArray(1), ByteArray(1), - ByteArray(1), ByteArray(1), ByteArray(1), ByteArray(1) + "V_C".toByteArray(), + "V_S".toByteArray(), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1) ) assertEquals(48, h.size) // SHA-384 output } @@ -181,9 +191,14 @@ class EcdhKeyExchangeTest { ecdh.generateClientKeys() val h = ecdh.computeExchangeHash( - "V_C".toByteArray(), "V_S".toByteArray(), - ByteArray(1), ByteArray(1), - ByteArray(1), ByteArray(1), ByteArray(1), ByteArray(1) + "V_C".toByteArray(), + "V_S".toByteArray(), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1), + ByteArray(1) ) assertEquals(64, h.size) // SHA-512 output } diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/MlKemHybridKeyExchangeTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/MlKemHybridKeyExchangeTest.kt index 6782eed..3bd3acb 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/MlKemHybridKeyExchangeTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/MlKemHybridKeyExchangeTest.kt @@ -36,21 +36,17 @@ class MlKemHybridKeyExchangeTest { private val fakeX25519SharedSecret = ByteArray(32) { ((it + 200) % 256).toByte() } private val mockMlKemProvider = object : MlKemProvider { - override fun generateKeyPair(): MlKemKeyPair = - MlKemKeyPair(fakeMlKemPublicKey.clone(), fakeMlKemPrivateKey.clone()) + override fun generateKeyPair(): MlKemKeyPair = MlKemKeyPair(fakeMlKemPublicKey.clone(), fakeMlKemPrivateKey.clone()) - override fun encapsulate(publicKey: ByteArray): MlKemEncapsulationResult = - MlKemEncapsulationResult(fakeMlKemCiphertext.clone(), fakeMlKemSharedSecret.clone()) + override fun encapsulate(publicKey: ByteArray): MlKemEncapsulationResult = MlKemEncapsulationResult(fakeMlKemCiphertext.clone(), fakeMlKemSharedSecret.clone()) - override fun decapsulate(privateKey: ByteArray, ciphertext: ByteArray): ByteArray = - fakeMlKemSharedSecret.clone() + override fun decapsulate(privateKey: ByteArray, ciphertext: ByteArray): ByteArray = fakeMlKemSharedSecret.clone() } private val mockX25519Provider = object : X25519Provider { override fun generatePrivateKey(): ByteArray = fakeX25519Private.clone() override fun publicFromPrivate(privateKey: ByteArray): ByteArray = fakeX25519Public.clone() - override fun computeSharedSecret(privateKey: ByteArray, publicKey: ByteArray): ByteArray = - fakeX25519SharedSecret.clone() + override fun computeSharedSecret(privateKey: ByteArray, publicKey: ByteArray): ByteArray = fakeX25519SharedSecret.clone() } @Test @@ -120,8 +116,7 @@ class MlKemHybridKeyExchangeTest { val zeroX25519Provider = object : X25519Provider { override fun generatePrivateKey(): ByteArray = fakeX25519Private.clone() override fun publicFromPrivate(privateKey: ByteArray): ByteArray = fakeX25519Public.clone() - override fun computeSharedSecret(privateKey: ByteArray, publicKey: ByteArray): ByteArray = - ByteArray(32) // all zeros + override fun computeSharedSecret(privateKey: ByteArray, publicKey: ByteArray): ByteArray = ByteArray(32) // all zeros } val kex = MlKemHybridKeyExchange(mockMlKemProvider, zeroX25519Provider) diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/OpenSshKeyWriterTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/OpenSshKeyWriterTest.kt index 13264e0..44a396e 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/OpenSshKeyWriterTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/OpenSshKeyWriterTest.kt @@ -24,10 +24,8 @@ import kotlin.test.assertTrue class OpenSshKeyWriterTest { - private fun readKey(resourcePath: String): String { - return javaClass.getResourceAsStream("/keys/$resourcePath")!! - .bufferedReader().readText() - } + private fun readKey(resourcePath: String): String = javaClass.getResourceAsStream("/keys/$resourcePath")!! + .bufferedReader().readText() private fun roundTrip(keyResource: String, passphrase: String? = null) { val original = PrivateKeyReader.read(readKey(keyResource), passphrase).jcaKeyPair diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/PemKeyWriterTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/PemKeyWriterTest.kt index 048fade..955bc53 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/PemKeyWriterTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/PemKeyWriterTest.kt @@ -17,17 +17,15 @@ package org.connectbot.sshlib.crypto import org.junit.Test -import java.security.interfaces.RSAPublicKey import java.security.interfaces.ECPublicKey +import java.security.interfaces.RSAPublicKey import kotlin.test.assertEquals import kotlin.test.assertTrue class PemKeyWriterTest { - private fun readKey(resourcePath: String): String { - return javaClass.getResourceAsStream("/keys/$resourcePath")!! - .bufferedReader().readText() - } + private fun readKey(resourcePath: String): String = javaClass.getResourceAsStream("/keys/$resourcePath")!! + .bufferedReader().readText() private fun roundTrip(keyResource: String, passphrase: String? = null) { val original = PrivateKeyReader.read(readKey(keyResource), passphrase).jcaKeyPair diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/PrivateKeyReaderTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/PrivateKeyReaderTest.kt index 87cf54e..be8db41 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/PrivateKeyReaderTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/PrivateKeyReaderTest.kt @@ -26,10 +26,8 @@ import kotlin.test.assertTrue class PrivateKeyReaderTest { - private fun readKey(resourcePath: String): String { - return javaClass.getResourceAsStream("/keys/$resourcePath")!! - .bufferedReader().readText() - } + private fun readKey(resourcePath: String): String = javaClass.getResourceAsStream("/keys/$resourcePath")!! + .bufferedReader().readText() // OpenSSH format - Ed25519 diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/SignatureGenerationTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/SignatureGenerationTest.kt index 79f2ee3..ed4b7a8 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/SignatureGenerationTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/SignatureGenerationTest.kt @@ -24,10 +24,8 @@ import kotlin.test.assertTrue class SignatureGenerationTest { - private fun readKey(resourcePath: String): String { - return javaClass.getResourceAsStream("/keys/$resourcePath")!! - .bufferedReader().readText() - } + private fun readKey(resourcePath: String): String = javaClass.getResourceAsStream("/keys/$resourcePath")!! + .bufferedReader().readText() private fun signAndVerify(privateKey: SshPrivateKey): Boolean { val data = "test data to sign".toByteArray() diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/protocol/CaptureTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/protocol/CaptureTest.kt index c63ddd9..c7274e2 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/protocol/CaptureTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/protocol/CaptureTest.kt @@ -71,10 +71,12 @@ class CaptureTest { println(" KEX algorithms: ${kexInit.kexAlgorithms().entries().data()}") println(" Host key algorithms: ${kexInit.serverHostKeyAlgorithms().entries().data()}") } + SshEnums.MessageType.SSH_MSG_NEWKEYS -> { println(" NEWKEYS received - switching to encrypted mode") break } + else -> { // KEX method specific messages need special handling if (packet.messageType().id() in 30..49) { @@ -163,6 +165,7 @@ class CaptureTest { val channelData = decryptedPacket.payload().body() as SshMsgChannelData println(" Channel ${channelData.recipientChannel()}: ${String(channelData.data().data())}") } + else -> {} } @@ -189,10 +192,12 @@ class CaptureTest { val init = kexdhPayload.body() as SshMsgKexdhInit println(" e: ${BigInteger(1, init.e().body()).toString(16).take(32)}...") } + SshEnums.KexDh.SSH_MSG_KEXDH_REPLY -> { val reply = kexdhPayload.body() as SshMsgKexdhReply println(" f: ${BigInteger(1, reply.f().body()).toString(16).take(32)}...") } + else -> {} } } diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/transport/ByteArrayTransport.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/transport/ByteArrayTransport.kt index 322cdd6..943b85c 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/transport/ByteArrayTransport.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/transport/ByteArrayTransport.kt @@ -27,7 +27,7 @@ import java.io.ByteArrayOutputStream * @param inputData Initial data available for reading */ class ByteArrayTransport( - inputData: ByteArray = byteArrayOf() + inputData: ByteArray = byteArrayOf(), ) : Transport { private var readBuffer = inputData diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/transport/ForwardingChannelTransportTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/transport/ForwardingChannelTransportTest.kt index 85fd6dc..38dfdbd 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/transport/ForwardingChannelTransportTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/transport/ForwardingChannelTransportTest.kt @@ -23,7 +23,10 @@ import io.mockk.mockk import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.test.runTest import org.connectbot.sshlib.client.ForwardingChannel -import org.junit.Assert.* +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Assert.fail import org.junit.Test class ForwardingChannelTransportTest { @@ -143,7 +146,7 @@ class ForwardingChannelTransportTest { every { fwdChannel.isOpen } returns false coEvery { fwdChannel.sendData(any()) } throws - kotlinx.coroutines.channels.ClosedSendChannelException("closed") + kotlinx.coroutines.channels.ClosedSendChannelException("closed") transport.close() diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/transport/KtorTcpTransportTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/transport/KtorTcpTransportTest.kt index 3bb0c8e..982a1d1 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/transport/KtorTcpTransportTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/transport/KtorTcpTransportTest.kt @@ -16,7 +16,9 @@ package org.connectbot.sshlib.transport -import io.ktor.utils.io.* +import io.ktor.utils.io.ByteChannel +import io.ktor.utils.io.ByteReadChannel +import io.ktor.utils.io.ByteWriteChannel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest @@ -151,7 +153,7 @@ class KtorTcpTransportTest { ) ) - val transport = KtorTcpTransport("example.com", 22, resolver, factory, IpVersion.IPv4_ONLY) + val transport = KtorTcpTransport("example.com", 22, resolver, factory, IpVersion.IPV4_ONLY) transport.connect() assertEquals(1, factory.connectionAttempts.size) @@ -171,7 +173,7 @@ class KtorTcpTransportTest { ) ) - val transport = KtorTcpTransport("example.com", 22, resolver, factory, IpVersion.IPv6_ONLY) + val transport = KtorTcpTransport("example.com", 22, resolver, factory, IpVersion.IPV6_ONLY) transport.connect() assertEquals(1, factory.connectionAttempts.size) @@ -185,7 +187,7 @@ class KtorTcpTransportTest { val resolver = MockAddressResolver(mapOf("example.com" to listOf(ipv6))) val factory = MockTcpSocketFactory(mapOf(ipv6 to { MockSocket() })) - val transport = KtorTcpTransport("example.com", 22, resolver, factory, IpVersion.IPv4_ONLY) + val transport = KtorTcpTransport("example.com", 22, resolver, factory, IpVersion.IPV4_ONLY) val ex = assertFailsWith { transport.connect() } @@ -199,7 +201,7 @@ class KtorTcpTransportTest { val resolver = MockAddressResolver(mapOf("example.com" to listOf(ipv4))) val factory = MockTcpSocketFactory(mapOf(ipv4 to { MockSocket() })) - val transport = KtorTcpTransport("example.com", 22, resolver, factory, IpVersion.IPv6_ONLY) + val transport = KtorTcpTransport("example.com", 22, resolver, factory, IpVersion.IPV6_ONLY) val ex = assertFailsWith { transport.connect() } @@ -221,7 +223,7 @@ class KtorTcpTransportTest { ) ) - val transport = KtorTcpTransport("example.com", 22, resolver, factory, IpVersion.IPv4_ONLY) + val transport = KtorTcpTransport("example.com", 22, resolver, factory, IpVersion.IPV4_ONLY) transport.connect() assertTrue(factory.connectionAttempts.all { it is java.net.Inet4Address }) @@ -249,13 +251,11 @@ class KtorTcpTransportTest { } class MockAddressResolver(private val hosts: Map>) : AddressResolver { - override suspend fun resolve(host: String): List { - return hosts[host] ?: emptyList() - } + override suspend fun resolve(host: String): List = hosts[host] ?: emptyList() } class MockTcpSocketFactory( - private val behaviors: Map TransportSocket> + private val behaviors: Map TransportSocket>, ) : TcpSocketFactory { val connectionAttempts = mutableListOf()