Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions sshlib/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
18 changes: 18 additions & 0 deletions sshlib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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(".") {
Expand Down
4 changes: 2 additions & 2 deletions sshlib/src/main/kotlin/org/connectbot/sshlib/AgentProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ interface AuthHandler {
suspend fun onKeyboardInteractivePrompt(
name: String,
instruction: String,
prompts: List<KeyboardInteractiveCallback.Prompt>
prompts: List<KeyboardInteractiveCallback.Prompt>,
): List<String>?

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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("*", ".*")
Expand Down Expand Up @@ -137,6 +137,6 @@ class KnownHostsVerifier(

private data class Entry(
val hosts: List<String>,
val key: PublicKey
val key: PublicKey,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ interface KeyboardInteractiveCallback {
name: String,
instruction: String,
prompts: List<Prompt>,
respond: suspend (responses: List<String>) -> Unit
respond: suspend (responses: List<String>) -> Unit,
)

data class Prompt(val text: String, val echo: Boolean)
Expand Down
67 changes: 34 additions & 33 deletions sshlib/src/main/kotlin/org/connectbot/sshlib/SshClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -224,7 +222,7 @@ class SshClient private constructor(
*/
suspend fun authenticateKeyboardInteractive(
username: String,
callback: KeyboardInteractiveCallback
callback: KeyboardInteractiveCallback,
): Boolean {
val conn = connection
if (conn == null) {
Expand Down Expand Up @@ -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).
Expand All @@ -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) {
Expand Down Expand Up @@ -358,23 +354,19 @@ 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.
*
* @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
}

/**
Expand Down Expand Up @@ -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) {
Expand All @@ -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)

/**
Expand All @@ -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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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)

/**
Expand All @@ -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) {
Expand All @@ -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)
Expand Down Expand Up @@ -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) {
Expand All @@ -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)
}
Expand Down
15 changes: 9 additions & 6 deletions sshlib/src/main/kotlin/org/connectbot/sshlib/SshClientConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand Down Expand Up @@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
12 changes: 3 additions & 9 deletions sshlib/src/main/kotlin/org/connectbot/sshlib/SshKeys.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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`).
Expand All @@ -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.
Expand Down
Loading
Loading