Connect Bot SSH Library
This is ConnectBot SSH library built with Kotlin. Internally it uses coroutines, protocol definition files, and a state machine to run the SSH protocol. It currently connects to SSH servers, authenticates, and provide interactive shell sessions.
The protocol parsing uses declarative Kaitai Struct specifications that auto-generate code from .ksy definitions. The internal state machine is defined in KStateMachine for clear separation of protocol states from the code that runs in reaction to state changes.
Features
SSH Client: Connect, authenticate, open shell sessions, read/write data
Protocol Parsing: Complete SSH wire protocol coverage (RFCs 4250-4256, 4419, 5656, 8308, 8709, 8731, 9142)
Channel I/O: Interactive shells with PTY, stdout/stderr streams, flow control
SFTP: File transfer with full read/write/stat/directory operations (draft-ietf-secsh-filexfer)
Port Forwarding: Local, remote, and dynamic (SOCKS5) port forwarding
Agent Forwarding: Forward SSH agent requests with session binding support
Transport: Pluggable transport layer (TCP via Ktor, or custom)
Algorithm Support
The library supports a wide range of modern SSH algorithms, including:
Authentication:
publickey(including FIDO2/SK),password,keyboard-interactiveHost Keys: Ed25519, Ed448, ECDSA, RSA (SHA-2)
Key Exchange: ML-KEM hybrid, Curve25519, ECDH, DH group-exchange
Encryption: ChaCha20-Poly1305, AES-GCM, AES-CTR
MACs: HMAC-SHA2 (including ETM variants)
For a complete list of supported algorithms and their respective RFCs, see docs/ALGORITHMS.md.
The defaults intentionally exclude SHA-1 key exchange and MACs, CBC/3DES ciphers, and ssh-rsa host-key signatures. These legacy algorithms remain available only through the explicit kexAlgorithms, hostKeyAlgorithms, encryptionAlgorithms, and macAlgorithms settings in SshClientConfig. RSA user authentication normally requires the server to advertise rsa-sha2-256 or rsa-sha2-512 through server-sig-algs. Explicitly including ssh-rsa in the configured host-key algorithm wishlist also permits the legacy RSA/SHA-1 signature when advertised, or as the base-key algorithm when that extension is absent.
Quick Start
Build
./gradlew buildUse the Test CLI Client
There is a "testapp" that allows you to try the library from a test client app. You can use it by running the following commands:
./gradlew :testapp:installDist
./testapp/build/install/testapp/bin/testapp user@host
./testapp/build/install/testapp/bin/testapp user@host -p 2222
# Enable more debug logging:
./testapp/build/install/testapp/bin/testapp -d user@hostLibrary API
val client = SshClient("example.com", port = 22, hostKeyVerifier = myVerifier)
check(client.connect() is ConnectResult.Success) { "SSH connection failed" }
check(client.authenticatePassword("user", "pass") is AuthResult.Success) {
"SSH authentication failed"
}
val session = checkNotNull(client.openSession()) { "Failed to open SSH session" }
check(session.requestPty()) { "Server rejected PTY request" }
check(session.requestShell()) { "Server rejected shell request" }
// Read/write
session.write("ls\n".toByteArray())
val output = session.read() // ByteArray? (null on EOF)
// Or use coroutine channels directly
session.stdout // ReceiveChannel<ByteArray>
session.stderr // ReceiveChannel<ByteArray>
// Clean up
session.close()
client.disconnect()SFTP File Transfer
val sftp = when (val result = client.openSftp()) {
is SftpResult.Success -> result.value
else -> error("Failed to open SFTP: $result")
}
try {
// List a directory
when (val result = sftp.listdir("/home/user")) {
is SftpResult.Success -> result.value.forEach { println(it.filename) }
is SftpResult.ServerError -> println("Server error: ${result.message}")
else -> println("Error: $result")
}
// Read a file
val handle = sftp.open("/home/user/file.txt", setOf(SftpOpenFlag.READ)).getOrThrow()
try {
val data = sftp.read(handle, 0L, 4096).getOrThrow() // ByteArray? (null on EOF)
} finally {
sftp.close(handle).getOrThrow()
}
} finally {
sftp.close()
}FIDO2 / Security Key Authentication
The library supports authentication with sk-ssh-ed25519@openssh.com and sk-ecdsa-sha2-nistp256@openssh.com keys. Callers provide their own FIDO2 stack and surface the resulting assertion through the library's helpers.
See docs/SK_AUTH.md for detailed implementation details and examples.
SSH Agent Forwarding
Enable SSH agent forwarding to allow remote servers to use your keys:
// Implement an agent provider
class MyAgentProvider : AgentProvider {
override suspend fun getIdentities(): AgentResult<List<AgentIdentity>> {
val keyBlob = loadPublicKeyBlob()
return AgentResult.Success(listOf(AgentIdentity(keyBlob, "my-key")))
}
override suspend fun signData(context: AgentSigningContext): AgentResult<ByteArray?> {
// Show approval UI to user with session context
val approved = showSigningPrompt(
"Remote server ${context.serverHostKey.joinToString("") { "%02x".format(it) }} wants to use your key",
"Session bound: ${context.isBound}"
)
return AgentResult.Success(if (approved) {
signWithPrivateKey(context.publicKeyBlob, context.dataToSign)
} else {
null // Deny the request
})
}
}
// Enable agent forwarding
val client = SshClient("bastion.example.com", hostKeyVerifier = myVerifier)
check(client.connect() is ConnectResult.Success) { "SSH connection failed" }
check(client.authenticatePassword("user", "pass") is AuthResult.Success) {
"SSH authentication failed"
}
client.enableAgentForwarding(MyAgentProvider())
// Now remote servers can use your agent through forwarding
val session = checkNotNull(client.openSession()) { "Failed to open SSH session" }
check(session.requestShell()) { "Server rejected shell request" }
// When you SSH from bastion to another server, it can request signaturesCompatibility Testing
The library is tested against multiple SSH server implementations using Docker (via Testcontainers):
OpenSSH 9.9p2 — full integration tests including port forwarding
AsyncSSH (Python) — compatibility tests for ciphers, key exchange, MACs, and public key auth
Dropbear — compatibility tests including ML-KEM post-quantum key exchange
Run integration tests with: ./gradlew :sshlib:test (requires Docker).
Current Limitations
Client-only (no server implementation)
License
Apache License 2.0 - See LICENSE file
Copyright
Copyright 2019-2026, Kenny Root