From 6707ebb4350b35daf75d29f2e93c640c058eef09 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Wed, 20 May 2026 18:24:03 -0700 Subject: [PATCH 1/6] chore: respond to sonarqube maint issues Extract constants, add comments about intent in empty methods, set cognitive complexity limits for special files --- .github/workflows/ci.yml | 2 +- build.gradle.kts | 29 ++++++++ protocol/build.gradle.kts | 1 + sshlib/build.gradle.kts | 10 --- .../org/connectbot/sshlib/AuthHandler.kt | 8 +- .../org/connectbot/sshlib/HostKeyVerifier.kt | 11 ++- .../kotlin/org/connectbot/sshlib/SshClient.kt | 42 ++++++----- .../connectbot/sshlib/client/SshConnection.kt | 74 ++++++++++--------- .../connectbot/sshlib/crypto/Algorithms.kt | 38 +++++----- .../crypto/DiffieHellmanGroupExchange.kt | 13 ++-- .../sshlib/crypto/JavaMlKemProvider.kt | 16 ++-- .../connectbot/sshlib/crypto/KeyDecryption.kt | 23 +++--- .../connectbot/sshlib/crypto/KeyEncryption.kt | 19 +++-- .../org/connectbot/sshlib/crypto/KeyTypes.kt | 10 ++- .../connectbot/sshlib/crypto/PacketAead.kt | 7 +- .../connectbot/sshlib/crypto/PacketCipher.kt | 7 +- .../org/connectbot/sshlib/crypto/PacketMac.kt | 7 +- .../sshlib/crypto/PrivateKeyReader.kt | 13 ++-- 18 files changed, 199 insertions(+), 131 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37030e3..6b4c7af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,4 +63,4 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} if: "${{ matrix.java == '25' && env.SONAR_TOKEN != '' }}" - run: ./gradlew :sshlib:sonar -Dsonar.projectVersion=${{ github.sha }} + run: ./gradlew sonar -Dsonar.projectVersion=${{ github.sha }} diff --git a/build.gradle.kts b/build.gradle.kts index 5da3a85..004dfd5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,6 +22,7 @@ plugins { alias(libs.plugins.publish) apply false alias(libs.plugins.kover) alias(libs.plugins.cyclonedx) + alias(libs.plugins.sonarqube) } allprojects { @@ -34,9 +35,37 @@ allprojects { } dependencies { + kover(project(":protocol")) kover(project(":sshlib")) } +sonar { + properties { + property("sonar.projectName", "ConnectBot SSH Library") + property("sonar.projectKey", "connectbot_cbssh") + property("sonar.organization", "connectbot") + property("sonar.host.url", "https://sonarcloud.io") + property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/kover/report.xml") + property("sonar.exclusions", "**/build/generated/**") + property("sonar.issue.ignore.multicriteria", "cognitiveComplexityConnection,cognitiveComplexitySftp,cognitiveComplexityTransport") + property("sonar.issue.ignore.multicriteria.cognitiveComplexityConnection.ruleKey", "kotlin:S3776") + property( + "sonar.issue.ignore.multicriteria.cognitiveComplexityConnection.resourceKey", + "**/src/main/kotlin/org/connectbot/sshlib/client/SshConnection.kt", + ) + property("sonar.issue.ignore.multicriteria.cognitiveComplexitySftp.ruleKey", "kotlin:S3776") + property( + "sonar.issue.ignore.multicriteria.cognitiveComplexitySftp.resourceKey", + "**/src/main/kotlin/org/connectbot/sshlib/client/sftp/SftpDispatcher.kt", + ) + property("sonar.issue.ignore.multicriteria.cognitiveComplexityTransport.ruleKey", "kotlin:S3776") + property( + "sonar.issue.ignore.multicriteria.cognitiveComplexityTransport.resourceKey", + "**/src/main/kotlin/org/connectbot/sshlib/transport/KtorTcpTransport.kt", + ) + } +} + spotless { ratchetFrom = "origin/main" diff --git a/protocol/build.gradle.kts b/protocol/build.gradle.kts index cf75aec..0e9741d 100644 --- a/protocol/build.gradle.kts +++ b/protocol/build.gradle.kts @@ -21,6 +21,7 @@ plugins { alias(libs.plugins.kotlin.jvm) alias(libs.plugins.publish) alias(libs.plugins.dokka) + alias(libs.plugins.kover) alias(libs.plugins.cyclonedx) `java-library` } diff --git a/sshlib/build.gradle.kts b/sshlib/build.gradle.kts index 3405f48..02e7205 100644 --- a/sshlib/build.gradle.kts +++ b/sshlib/build.gradle.kts @@ -24,7 +24,6 @@ plugins { alias(libs.plugins.metalava) alias(libs.plugins.kover) alias(libs.plugins.cyclonedx) - alias(libs.plugins.sonarqube) `java-library` } @@ -147,12 +146,3 @@ mavenPublishing { } } } - -sonar { - properties { - property("sonar.projectKey", "connectbot_cbssh") - property("sonar.organization", "connectbot") - property("sonar.host.url", "https://sonarcloud.io") - property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/kover/report.xml") - } -} diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/AuthHandler.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/AuthHandler.kt index 21d8031..dbe398d 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/AuthHandler.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/AuthHandler.kt @@ -29,7 +29,9 @@ interface AuthHandler { * Called when the server reports which authentication methods are available. * Override to observe or log. */ - suspend fun onAuthMethodsAvailable(methods: Set) {} + suspend fun onAuthMethodsAvailable(methods: Set) { + // Optional notification hook; default handlers do not need to observe it. + } /** * Return public keys to probe. Empty list skips public key auth. @@ -76,7 +78,9 @@ interface AuthHandler { * Called when the server sends an authentication banner (SSH_MSG_USERAUTH_BANNER). * This is often used for out-of-band authentication instructions (e.g., a URL to visit). */ - suspend fun onBanner(message: String) {} + suspend fun onBanner(message: String) { + // Optional notification hook; default handlers may ignore banners. + } } /** diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/HostKeyVerifier.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/HostKeyVerifier.kt index 3327f94..205df47 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/HostKeyVerifier.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/HostKeyVerifier.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,8 +35,13 @@ interface HostKeyVerifier { */ suspend fun verify(key: PublicKey): Boolean - suspend fun addKeys(keys: List) {} - suspend fun removeKeys(keys: List) {} + suspend fun addKeys(keys: List) { + // Optional persistence hook; read-only verifiers have nothing to store. + } + + suspend fun removeKeys(keys: List) { + // Optional persistence hook; read-only verifiers have nothing to remove. + } } /** diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClient.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClient.kt index e77e759..3fa47d5 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClient.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClient.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -78,6 +78,10 @@ class SshClient private constructor( ) { companion object { private val logger = LoggerFactory.getLogger(SshClient::class.java) + private const val ERROR_NOT_CONNECTED = "Not connected" + private const val ERROR_NOT_CONNECTED_CONNECT_FIRST = "Not connected - call connect() first" + private const val ERROR_NOT_AUTHENTICATED = "Not authenticated" + private const val LOCALHOST = "127.0.0.1" /** * Create an SshClient for TCP connection to the specified host. @@ -198,8 +202,8 @@ class SshClient private constructor( suspend fun authenticatePassword(username: String, password: String): AuthResult { val conn = connection if (conn == null) { - logger.error("Not connected - call connect() first") - return AuthResult.Error("Not connected") + logger.error(ERROR_NOT_CONNECTED_CONNECT_FIRST) + return AuthResult.Error(ERROR_NOT_CONNECTED) } return try { @@ -234,8 +238,8 @@ class SshClient private constructor( ): AuthResult { val conn = connection if (conn == null) { - logger.error("Not connected - call connect() first") - return AuthResult.Error("Not connected") + logger.error(ERROR_NOT_CONNECTED_CONNECT_FIRST) + return AuthResult.Error(ERROR_NOT_CONNECTED) } return try { @@ -287,8 +291,8 @@ class SshClient private constructor( ): AuthResult { val conn = connection if (conn == null) { - logger.error("Not connected - call connect() first") - return AuthResult.Error("Not connected") + logger.error(ERROR_NOT_CONNECTED_CONNECT_FIRST) + return AuthResult.Error(ERROR_NOT_CONNECTED) } return try { @@ -325,8 +329,8 @@ class SshClient private constructor( suspend fun authenticate(username: String, handler: AuthHandler): AuthResult { val conn = connection if (conn == null) { - logger.error("Not connected - call connect() first") - return AuthResult.Error("Not connected") + logger.error(ERROR_NOT_CONNECTED_CONNECT_FIRST) + return AuthResult.Error(ERROR_NOT_CONNECTED) } return try { @@ -427,7 +431,7 @@ class SshClient private constructor( val conn = connection if (conn == null || !authenticated) { logger.error("Not authenticated - call connect() and authenticate first") - return SftpResult.IoError(IllegalStateException("Not authenticated")) + return SftpResult.IoError(IllegalStateException(ERROR_NOT_AUTHENTICATED)) } return try { @@ -463,7 +467,7 @@ class SshClient private constructor( ): PortForwarder? { val conn = connection if (conn == null || !authenticated) { - logger.error("Not authenticated") + logger.error(ERROR_NOT_AUTHENTICATED) return null } @@ -487,7 +491,7 @@ class SshClient private constructor( bindPort: Int, remoteHost: String, remotePort: Int, - ): PortForwarder? = localPortForward(InetSocketAddress("127.0.0.1", bindPort), remoteHost, remotePort) + ): PortForwarder? = localPortForward(InetSocketAddress(LOCALHOST, bindPort), remoteHost, remotePort) /** * Start remote port forwarding (RFC 4254 section 7.1). @@ -509,7 +513,7 @@ class SshClient private constructor( ): PortForwarder? { val conn = connection if (conn == null || !authenticated) { - logger.error("Not authenticated") + logger.error(ERROR_NOT_AUTHENTICATED) return null } @@ -537,7 +541,7 @@ class SshClient private constructor( ): PortForwarder? { val conn = connection if (conn == null || !authenticated) { - logger.error("Not authenticated") + logger.error(ERROR_NOT_AUTHENTICATED) return null } @@ -559,7 +563,7 @@ class SshClient private constructor( suspend fun dynamicPortForward( bindPort: Int, authenticator: Socks5Authenticator? = null, - ): PortForwarder? = dynamicPortForward(InetSocketAddress("127.0.0.1", bindPort), authenticator) + ): PortForwarder? = dynamicPortForward(InetSocketAddress(LOCALHOST, bindPort), authenticator) /** * Forward a pair of streams through an SSH direct-tcpip channel. @@ -580,12 +584,12 @@ class SshClient private constructor( writeChannel: ByteWriteChannel, remoteHost: String, remotePort: Int, - originAddr: String = "127.0.0.1", + originAddr: String = LOCALHOST, originPort: Int = 0, ): StreamForwarder? { val conn = connection if (conn == null || !authenticated) { - logger.error("Not authenticated") + logger.error(ERROR_NOT_AUTHENTICATED) return null } @@ -635,12 +639,12 @@ class SshClient private constructor( fun openDirectTcpipTransport( remoteHost: String, remotePort: Int, - originAddr: String = "127.0.0.1", + originAddr: String = LOCALHOST, originPort: Int = 0, ): TransportFactory? { val conn = connection if (conn == null || !authenticated) { - logger.error("Not authenticated") + logger.error(ERROR_NOT_AUTHENTICATED) return null } 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 dfe29a3..77fc3e0 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/SshConnection.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/SshConnection.kt @@ -168,17 +168,25 @@ class SshConnection( companion object { private val logger = LoggerFactory.getLogger(SshConnection::class.java) + private const val KEX_EXT_INFO_C = "ext-info-c" + private const val SERVICE_SSH_CONNECTION = "ssh-connection" + private const val KEY_TYPE_SSH_RSA = "ssh-rsa" + private const val METHOD_PUBLICKEY_HOSTBOUND = "publickey-hostbound-v00@openssh.com" + private const val CHANNEL_FORWARDED_TCPIP = "forwarded-tcpip" + private const val ERROR_SESSION_ID_NOT_ESTABLISHED = "Session ID not established" + private const val ERROR_CLIENT_PUBLIC_KEY_NOT_GENERATED = "Client public key not generated" + private const val ERROR_NO_KEX_ALGORITHM_INITIALIZED = "No KEX algorithm initialized" private fun stripExtInfoC(kexAlgorithms: String): String = kexAlgorithms.split(",") - .filter { it.isNotEmpty() && it != "ext-info-c" } + .filter { it.isNotEmpty() && it != KEX_EXT_INFO_C } .joinToString(",") private fun appendExtInfoC(kexAlgorithms: String): String { val algorithms = kexAlgorithms.split(",").filter { it.isNotEmpty() } - return if ("ext-info-c" in algorithms) { + return if (KEX_EXT_INFO_C in algorithms) { kexAlgorithms } else { - (algorithms + "ext-info-c").joinToString(",") + (algorithms + KEX_EXT_INFO_C).joinToString(",") } } @@ -448,7 +456,7 @@ class SshConnection( try { val req = SshMsgUserauthRequest().apply { setUserName(createAsciiString(username)) - setServiceName(createAsciiString("ssh-connection")) + setServiceName(createAsciiString(SERVICE_SSH_CONNECTION)) setMethodName(createAsciiString("password")) val passAuth = UserauthRequestPassword().apply { @@ -498,7 +506,7 @@ class SshConnection( try { val req = SshMsgUserauthRequest().apply { setUserName(createAsciiString(username)) - setServiceName(createAsciiString("ssh-connection")) + setServiceName(createAsciiString(SERVICE_SSH_CONNECTION)) setMethodName(createAsciiString("keyboard-interactive")) val kbdInteractive = UserauthRequestKeyboardInteractive().apply { @@ -584,11 +592,11 @@ class SshConnection( */ internal suspend fun authenticatePublicKey(username: String, privateKey: SshPrivateKey): PublicAuthResult { try { - val sid = sessionId ?: throw SshException("Session ID not established") + val sid = sessionId ?: throw SshException(ERROR_SESSION_ID_NOT_ESTABLISHED) val publicKeyBlob = SshPublicKeyEncoder.encode(privateKey.jcaKeyPair, privateKey.keyType) - val sigAlgorithmName = if (privateKey.keyType == "ssh-rsa") { + val sigAlgorithmName = if (privateKey.keyType == KEY_TYPE_SSH_RSA) { negotiateRsaAlgorithm() } else { privateKey.signatureAlgorithm @@ -600,9 +608,9 @@ class SshConnection( val useHostBound = serverAdvertisesHostBound && hostKeyBlob != null val signatureData = if (useHostBound && hostKeyBlob != null) { - buildHostBoundSignatureData(sid, username, "ssh-connection", sigAlgorithmName, publicKeyBlob, hostKeyBlob) + buildHostBoundSignatureData(sid, username, SERVICE_SSH_CONNECTION, sigAlgorithmName, publicKeyBlob, hostKeyBlob) } else { - buildSignatureData(sid, username, "ssh-connection", sigAlgorithmName, publicKeyBlob) + buildSignatureData(sid, username, SERVICE_SSH_CONNECTION, sigAlgorithmName, publicKeyBlob) } val signature = sigEntry.algorithm.sign( @@ -613,10 +621,10 @@ class SshConnection( val req = SshMsgUserauthRequest().apply { setUserName(createAsciiString(username)) - setServiceName(createAsciiString("ssh-connection")) + setServiceName(createAsciiString(SERVICE_SSH_CONNECTION)) if (useHostBound && hostKeyBlob != null) { - setMethodName(createAsciiString("publickey-hostbound-v00@openssh.com")) + setMethodName(createAsciiString(METHOD_PUBLICKEY_HOSTBOUND)) val pubkeyAuth = UserauthRequestPublickeyHostbound().apply { setHasSignature(1) setPublicKeyAlgorithmName(createAsciiString(sigAlgorithmName)) @@ -698,7 +706,7 @@ class SshConnection( setMessageType(byteArrayOf(50)) setUserName(createByteString(username.toByteArray(Charsets.UTF_8))) setServiceName(createByteString(serviceName.toByteArray(Charsets.US_ASCII))) - setMethodName(createByteString("publickey-hostbound-v00@openssh.com".toByteArray(Charsets.US_ASCII))) + setMethodName(createByteString(METHOD_PUBLICKEY_HOSTBOUND.toByteArray(Charsets.US_ASCII))) setHasSignature(byteArrayOf(1)) setPublicKeyAlgorithmName(createByteString(algorithmName.toByteArray(Charsets.US_ASCII))) setPublicKeyBlob(createByteString(publicKeyBlob)) @@ -805,7 +813,7 @@ class SshConnection( handler: AuthHandler, channel: Channel, ): InternalAuthResult { - val effectiveAlgorithmName = if (keyBlobAlgorithmName(key.publicKeyBlob) == "ssh-rsa") { + val effectiveAlgorithmName = if (keyBlobAlgorithmName(key.publicKeyBlob) == KEY_TYPE_SSH_RSA) { negotiateRsaAlgorithm() } else { key.algorithmName @@ -828,27 +836,27 @@ class SshConnection( handler: AuthHandler, channel: Channel, ): Boolean { - val sid = sessionId ?: throw SshException("Session ID not established") + val sid = sessionId ?: throw SshException(ERROR_SESSION_ID_NOT_ESTABLISHED) val hostKeyBlob = serverHostKeyBlob val useHostBound = serverAdvertisesHostBound && hostKeyBlob != null - val effectiveAlgorithmName = if (keyBlobAlgorithmName(key.publicKeyBlob) == "ssh-rsa") { + val effectiveAlgorithmName = if (keyBlobAlgorithmName(key.publicKeyBlob) == KEY_TYPE_SSH_RSA) { negotiateRsaAlgorithm() } else { key.algorithmName } val signatureData = if (useHostBound && hostKeyBlob != null) { - buildHostBoundSignatureData(sid, username, "ssh-connection", effectiveAlgorithmName, key.publicKeyBlob, hostKeyBlob) + buildHostBoundSignatureData(sid, username, SERVICE_SSH_CONNECTION, effectiveAlgorithmName, key.publicKeyBlob, hostKeyBlob) } else { - buildSignatureData(sid, username, "ssh-connection", effectiveAlgorithmName, key.publicKeyBlob) + buildSignatureData(sid, username, SERVICE_SSH_CONNECTION, effectiveAlgorithmName, key.publicKeyBlob) } val signingKey = if (effectiveAlgorithmName != key.algorithmName) key.copy(algorithmName = effectiveAlgorithmName) else key val signature = handler.onSignatureRequest(signingKey, signatureData) ?: return false if (useHostBound && hostKeyBlob != null) { - sendAuthRequest(username, "publickey-hostbound-v00@openssh.com") { + sendAuthRequest(username, METHOD_PUBLICKEY_HOSTBOUND) { val pubkeyAuth = UserauthRequestPublickeyHostbound().apply { setHasSignature(1) setPublicKeyAlgorithmName(createAsciiString(effectiveAlgorithmName)) @@ -984,7 +992,7 @@ class SshConnection( ) { val req = SshMsgUserauthRequest().apply { setUserName(createAsciiString(username)) - setServiceName(createAsciiString("ssh-connection")) + setServiceName(createAsciiString(SERVICE_SSH_CONNECTION)) setMethodName(createAsciiString(method)) configure() _check() @@ -1151,7 +1159,7 @@ class SshConnection( clientPublicKey = dh.generateClientKeys() val pubKey = clientPublicKey - ?: throw SshException("Client public key not generated") + ?: throw SshException(ERROR_CLIENT_PUBLIC_KEY_NOT_GENERATED) val msg = SshMsgKexdhInit().apply { setE(createMpint(pubKey)) @@ -1169,7 +1177,7 @@ class SshConnection( clientPublicKey = ecdh.generateClientKeys() val pubKey = clientPublicKey - ?: throw SshException("Client public key not generated") + ?: throw SshException(ERROR_CLIENT_PUBLIC_KEY_NOT_GENERATED) val msg = SshMsgKexEcdhInit().apply { setQC(createByteString(pubKey)) @@ -1267,11 +1275,11 @@ class SshConnection( } private suspend fun completeKex(serverHostKey: ByteArray, serverPublicKey: ByteArray, signature: ByteArray) { - val kexAlg = kex ?: throw SshException("No KEX algorithm initialized") + val kexAlg = kex ?: throw SshException(ERROR_NO_KEX_ALGORITHM_INITIALIZED) val sv = serverVersion ?: throw SshException("Server version not received") val cki = clientKexInit ?: throw SshException("Client KEX_INIT not sent") val ski = serverKexInit ?: throw SshException("Server KEX_INIT not received") - val cpk = clientPublicKey ?: throw SshException("Client public key not generated") + val cpk = clientPublicKey ?: throw SshException(ERROR_CLIENT_PUBLIC_KEY_NOT_GENERATED) sharedSecret = kexAlg.computeSharedSecret(serverPublicKey) @@ -1493,8 +1501,8 @@ class SshConnection( val secret = sharedSecret ?: throw SshException("Shared secret not computed") val hash = exchangeHash ?: throw SshException("Exchange hash not computed") - val sid = sessionId ?: throw SshException("Session ID not established") - val kexAlg = kex ?: throw SshException("No KEX algorithm initialized") + val sid = sessionId ?: throw SshException(ERROR_SESSION_ID_NOT_ESTABLISHED) + val kexAlg = kex ?: throw SshException(ERROR_NO_KEX_ALGORITHM_INITIALIZED) val keyDerivation = KeyDerivation( secret, @@ -1552,8 +1560,8 @@ class SshConnection( val secret = sharedSecret ?: throw SshException("Shared secret not computed") val hash = exchangeHash ?: throw SshException("Exchange hash not computed") - val sid = sessionId ?: throw SshException("Session ID not established") - val kexAlg = kex ?: throw SshException("No KEX algorithm initialized") + val sid = sessionId ?: throw SshException(ERROR_SESSION_ID_NOT_ESTABLISHED) + val kexAlg = kex ?: throw SshException(ERROR_NO_KEX_ALGORITHM_INITIALIZED) val keyDerivation = KeyDerivation( secret, @@ -1732,7 +1740,7 @@ class SshConnection( logger.info("Accepted agent channel: local=$localChannelNumber, remote=$senderChannel") } - "forwarded-tcpip" -> { + CHANNEL_FORWARDED_TCPIP -> { handleForwardedTcpip(msg, senderChannel, initialWindow, maxPacketSize) } @@ -1754,8 +1762,8 @@ class SshConnection( try { val channelData = msg.channelSpecificData() if (channelData !is ChannelOpenForwardedTcpip) { - logger.warn("Failed to parse forwarded-tcpip channel data") - rejectChannelOpen(senderChannel, "forwarded-tcpip") + logger.warn("Failed to parse $CHANNEL_FORWARDED_TCPIP channel data") + rejectChannelOpen(senderChannel, CHANNEL_FORWARDED_TCPIP) return } @@ -1768,14 +1776,14 @@ class SshConnection( val handler = remoteForwarders[key] if (handler == null) { logger.warn("No remote forwarder registered for $key") - rejectChannelOpen(senderChannel, "forwarded-tcpip") + rejectChannelOpen(senderChannel, CHANNEL_FORWARDED_TCPIP) return } handler(connectedAddr, connectedPort, originAddr, originPort, senderChannel, initialWindow, maxPacketSize) } catch (e: Exception) { - logger.error("Failed to handle forwarded-tcpip", e) - rejectChannelOpen(senderChannel, "forwarded-tcpip") + logger.error("Failed to handle $CHANNEL_FORWARDED_TCPIP", e) + rejectChannelOpen(senderChannel, CHANNEL_FORWARDED_TCPIP) } } 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 548ba16..d7c8c0b 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Algorithms.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Algorithms.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,10 @@ package org.connectbot.sshlib.crypto +private const val HASH_SHA256 = "SHA-256" +private const val HASH_SHA512 = "SHA-512" +private const val KEY_TYPE_SSH_RSA = "ssh-rsa" + internal sealed interface EncryptionInstance { data class Cipher(val cipher: PacketCipher) : EncryptionInstance data class Aead(val aead: PacketAead) : EncryptionInstance @@ -180,21 +184,21 @@ internal enum class KexEntry( ) { MLKEM768X25519_SHA256( "mlkem768x25519-sha256", - "SHA-256", + HASH_SHA256, KexType.ECDH, true, { MlKemHybridKeyExchange() }, ), CURVE25519_SHA256( "curve25519-sha256", - "SHA-256", + HASH_SHA256, KexType.ECDH, false, { Curve25519KeyExchange() }, ), ECDH_SHA2_NISTP256( "ecdh-sha2-nistp256", - "SHA-256", + HASH_SHA256, KexType.ECDH, false, { EcdhKeyExchange("nistp256") }, @@ -208,38 +212,38 @@ internal enum class KexEntry( ), ECDH_SHA2_NISTP521( "ecdh-sha2-nistp521", - "SHA-512", + HASH_SHA512, KexType.ECDH, false, { EcdhKeyExchange("nistp521") }, ), DH_GROUP18_SHA512( "diffie-hellman-group18-sha512", - "SHA-512", + HASH_SHA512, KexType.DH, false, - { DiffieHellman("SHA-512", DhGroups.GROUP18_P, DhGroups.GENERATOR) }, + { DiffieHellman(HASH_SHA512, DhGroups.GROUP18_P, DhGroups.GENERATOR) }, ), DH_GROUP16_SHA512( "diffie-hellman-group16-sha512", - "SHA-512", + HASH_SHA512, KexType.DH, false, - { DiffieHellman("SHA-512", DhGroups.GROUP16_P, DhGroups.GENERATOR) }, + { DiffieHellman(HASH_SHA512, DhGroups.GROUP16_P, DhGroups.GENERATOR) }, ), DH_GROUP_EXCHANGE_SHA256( "diffie-hellman-group-exchange-sha256", - "SHA-256", + HASH_SHA256, KexType.DH_GEX, false, - { DiffieHellmanGroupExchange("SHA-256") }, + { DiffieHellmanGroupExchange(HASH_SHA256) }, ), DH_GROUP14_SHA256( "diffie-hellman-group14-sha256", - "SHA-256", + HASH_SHA256, KexType.DH, false, - { DiffieHellman("SHA-256", DhGroups.GROUP14_P, DhGroups.GENERATOR) }, + { DiffieHellman(HASH_SHA256, DhGroups.GROUP14_P, DhGroups.GENERATOR) }, ), DH_GROUP14_SHA1( "diffie-hellman-group14-sha1", @@ -287,7 +291,7 @@ 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(KEY_TYPE_SSH_RSA, RsaSignatureAlgorithm), ; companion object { @@ -297,7 +301,7 @@ internal enum class SignatureEntry( fun fromSshName(name: String): SignatureEntry? = entries.firstOrNull { it.sshName == name } - private val rsaPreferenceOrder = listOf("rsa-sha2-512", "rsa-sha2-256", "ssh-rsa") + private val rsaPreferenceOrder = listOf("rsa-sha2-512", "rsa-sha2-256", KEY_TYPE_SSH_RSA) /** * Picks the best RSA signing algorithm given the server's advertised list. @@ -305,8 +309,8 @@ internal enum class SignatureEntry( * or if no supported RSA algorithms were advertised. */ fun negotiateRsaAlgorithm(serverSigAlgs: Set?): String { - if (serverSigAlgs == null) return "ssh-rsa" - return rsaPreferenceOrder.firstOrNull { it in serverSigAlgs } ?: "ssh-rsa" + if (serverSigAlgs == null) return KEY_TYPE_SSH_RSA + return rsaPreferenceOrder.firstOrNull { it in serverSigAlgs } ?: KEY_TYPE_SSH_RSA } } } 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 c4711ba..10053e2 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/DiffieHellmanGroupExchange.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/DiffieHellmanGroupExchange.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import java.security.SecureRandom internal class DiffieHellmanGroupExchange(override val hashAlgorithm: String) : KexAlgorithm { companion object { private val secureRandom = SecureRandom() + private const val ERROR_GROUP_NOT_SET = "DH group not set; call setGroup() first" } val min = 2048 @@ -48,8 +49,8 @@ internal class DiffieHellmanGroupExchange(override val hashAlgorithm: String) : } override fun generateClientKeys(): 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") + val p = this.p ?: throw SshException(ERROR_GROUP_NOT_SET) + val g = this.g ?: throw SshException(ERROR_GROUP_NOT_SET) val x = BigInteger(p.bitLength(), secureRandom).mod(p - BigInteger.ONE) + BigInteger.ONE privateKey = x @@ -60,7 +61,7 @@ internal class DiffieHellmanGroupExchange(override val hashAlgorithm: String) : override fun computeSharedSecret(serverPublicKey: ByteArray): ByteArray { val x = privateKey ?: throw SshException("Client keys not generated; call generateClientKeys() first") - val p = this.p ?: throw SshException("DH group not set; call setGroup() first") + val p = this.p ?: throw SshException(ERROR_GROUP_NOT_SET) val f = BigInteger(1, serverPublicKey) if (f <= BigInteger.ONE || f >= p - BigInteger.ONE) { @@ -89,8 +90,8 @@ internal class DiffieHellmanGroupExchange(override val hashAlgorithm: String) : serverPublicKey: 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") + val p = this.p ?: throw SshException(ERROR_GROUP_NOT_SET) + val g = this.g ?: throw SshException(ERROR_GROUP_NOT_SET) val transcript = ByteArrayOutputStream() 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 f46e589..596e61c 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/JavaMlKemProvider.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/JavaMlKemProvider.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,8 @@ import javax.crypto.SecretKey internal class JavaMlKemProvider : MlKemProvider { companion object { private const val MLKEM768_PUBLIC_KEY_SIZE = 1184 + private const val KEM_CLASS_NAME = "javax.crypto.KEM" + private const val ML_KEM_ALGORITHM = "ML-KEM" // X.509 wrapper for ML-KEM-768 public keys private val X509_PREFIX = byteArrayOf( @@ -72,9 +74,9 @@ internal class JavaMlKemProvider : MlKemProvider { init { try { - val kemClass = Class.forName("javax.crypto.KEM") + val kemClass = Class.forName(KEM_CLASS_NAME) val getInstance = kemClass.getMethod("getInstance", String::class.java) - kemInstance = getInstance.invoke(null, "ML-KEM") + kemInstance = getInstance.invoke(null, ML_KEM_ALGORITHM) } catch (e: Exception) { throw IOException("Failed to initialize Java KEM API", e) } @@ -98,10 +100,10 @@ internal class JavaMlKemProvider : MlKemProvider { override fun encapsulate(publicKey: ByteArray): MlKemEncapsulationResult { try { val x509Encoded = wrapRawMlKemPublicKey(publicKey) - val kf = KeyFactory.getInstance("ML-KEM") + val kf = KeyFactory.getInstance(ML_KEM_ALGORITHM) val pubKey = kf.generatePublic(X509EncodedKeySpec(x509Encoded)) - val kemClass = Class.forName("javax.crypto.KEM") + val kemClass = Class.forName(KEM_CLASS_NAME) val newEncapsulator = kemClass.getMethod("newEncapsulator", PublicKey::class.java) val encapsulator = newEncapsulator.invoke(kemInstance, pubKey) @@ -125,10 +127,10 @@ internal class JavaMlKemProvider : MlKemProvider { override fun decapsulate(privateKey: ByteArray, ciphertext: ByteArray): ByteArray { try { - val kf = KeyFactory.getInstance("ML-KEM") + val kf = KeyFactory.getInstance(ML_KEM_ALGORITHM) val privKey: PrivateKey = kf.generatePrivate(PKCS8EncodedKeySpec(privateKey)) - val kemClass = Class.forName("javax.crypto.KEM") + val kemClass = Class.forName(KEM_CLASS_NAME) val newDecapsulator = kemClass.getMethod("newDecapsulator", PrivateKey::class.java) val decapsulator = newDecapsulator.invoke(kemInstance, privKey) 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 36ee328..cf267f6 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyDecryption.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyDecryption.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,9 @@ import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec +private const val AES_CBC_NO_PADDING = "AES/CBC/NoPadding" +private const val AES_CTR_NO_PADDING = "AES/CTR/NoPadding" + internal object KeyDecryption { fun decryptOpenSsh( @@ -34,12 +37,12 @@ internal object KeyDecryption { cipherName: String, ): ByteArray { val (jcaCipher, keySize, ivSize) = when (cipherName.lowercase()) { - "aes256-ctr" -> Triple("AES/CTR/NoPadding", 32, 16) - "aes256-cbc" -> Triple("AES/CBC/NoPadding", 32, 16) - "aes128-ctr" -> Triple("AES/CTR/NoPadding", 16, 16) - "aes128-cbc" -> Triple("AES/CBC/NoPadding", 16, 16) - "aes192-ctr" -> Triple("AES/CTR/NoPadding", 24, 16) - "aes192-cbc" -> Triple("AES/CBC/NoPadding", 24, 16) + "aes256-ctr" -> Triple(AES_CTR_NO_PADDING, 32, 16) + "aes256-cbc" -> Triple(AES_CBC_NO_PADDING, 32, 16) + "aes128-ctr" -> Triple(AES_CTR_NO_PADDING, 16, 16) + "aes128-cbc" -> Triple(AES_CBC_NO_PADDING, 16, 16) + "aes192-ctr" -> Triple(AES_CTR_NO_PADDING, 24, 16) + "aes192-cbc" -> Triple(AES_CBC_NO_PADDING, 24, 16) else -> throw SshException("Unsupported OpenSSH cipher: $cipherName") } @@ -63,9 +66,9 @@ internal object KeyDecryption { val (jcaCipher, keySize) = when (cipherName.uppercase()) { "DES-EDE3-CBC" -> "DESede/CBC/NoPadding" to 24 "DES-CBC" -> "DES/CBC/NoPadding" to 8 - "AES-128-CBC" -> "AES/CBC/NoPadding" to 16 - "AES-192-CBC" -> "AES/CBC/NoPadding" to 24 - "AES-256-CBC" -> "AES/CBC/NoPadding" to 32 + "AES-128-CBC" -> AES_CBC_NO_PADDING to 16 + "AES-192-CBC" -> AES_CBC_NO_PADDING to 24 + "AES-256-CBC" -> AES_CBC_NO_PADDING to 32 else -> throw SshException("Unsupported PEM cipher: $cipherName") } 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 8f87ad9..fa7f195 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyEncryption.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyEncryption.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,9 @@ import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec +private const val AES_CBC_NO_PADDING = "AES/CBC/NoPadding" +private const val AES_CTR_NO_PADDING = "AES/CTR/NoPadding" + internal object KeyEncryption { fun encryptPem( @@ -34,9 +37,9 @@ internal object KeyEncryption { val (jcaCipher, keySize) = when (cipherName.uppercase()) { "DES-EDE3-CBC" -> "DESede/CBC/NoPadding" to 24 "DES-CBC" -> "DES/CBC/NoPadding" to 8 - "AES-128-CBC" -> "AES/CBC/NoPadding" to 16 - "AES-192-CBC" -> "AES/CBC/NoPadding" to 24 - "AES-256-CBC" -> "AES/CBC/NoPadding" to 32 + "AES-128-CBC" -> AES_CBC_NO_PADDING to 16 + "AES-192-CBC" -> AES_CBC_NO_PADDING to 24 + "AES-256-CBC" -> AES_CBC_NO_PADDING to 32 else -> throw SshException("Unsupported PEM cipher: $cipherName") } @@ -66,10 +69,10 @@ internal object KeyEncryption { cipherName: String, ): ByteArray { val (jcaCipher, keySize, ivSize) = when (cipherName.lowercase()) { - "aes256-ctr" -> Triple("AES/CTR/NoPadding", 32, 16) - "aes256-cbc" -> Triple("AES/CBC/NoPadding", 32, 16) - "aes128-ctr" -> Triple("AES/CTR/NoPadding", 16, 16) - "aes128-cbc" -> Triple("AES/CBC/NoPadding", 16, 16) + "aes256-ctr" -> Triple(AES_CTR_NO_PADDING, 32, 16) + "aes256-cbc" -> Triple(AES_CBC_NO_PADDING, 32, 16) + "aes128-ctr" -> Triple(AES_CTR_NO_PADDING, 16, 16) + "aes128-cbc" -> Triple(AES_CBC_NO_PADDING, 16, 16) else -> throw SshException("Unsupported OpenSSH cipher: $cipherName") } 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 ff9d551..8902b4a 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyTypes.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/KeyTypes.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,13 +22,15 @@ import java.security.KeyPair import java.security.PublicKey import java.security.interfaces.ECPublicKey +private const val KEY_TYPE_ED25519 = "ssh-ed25519" + internal fun inferKeyType(publicKey: PublicKey): String = when (publicKey.algorithm) { - "Ed25519" -> "ssh-ed25519" + "Ed25519" -> KEY_TYPE_ED25519 "Ed448" -> "ssh-ed448" "EdDSA" -> { - if (publicKey.encoded.size <= 44) "ssh-ed25519" else "ssh-ed448" + if (publicKey.encoded.size <= 44) KEY_TYPE_ED25519 else "ssh-ed448" } "EC", "ECDSA" -> { @@ -45,7 +47,7 @@ internal fun inferKeyType(publicKey: PublicKey): String = when (publicKey.algori else -> { if (isEd25519Key(publicKey)) { - "ssh-ed25519" + KEY_TYPE_ED25519 } else { throw SshException("Unsupported key type: ${publicKey.algorithm}") } 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 2984214..7bdbee8 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PacketAead.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PacketAead.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,10 @@ internal data class AeadResult(val ciphertext: ByteArray, val tag: ByteArray) * as AAD (authenticated but not encrypted). */ internal interface PacketAead : Destroyable { - override fun destroy() {} + override fun destroy() { + // Stateless implementations have no key material to clear. + } + override fun isDestroyed() = false val tagLength: Int diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PacketCipher.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PacketCipher.kt index bec664c..5ec0643 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PacketCipher.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PacketCipher.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,10 @@ import javax.security.auth.Destroyable * Interface for SSH packet encryption/decryption. */ internal interface PacketCipher : Destroyable { - override fun destroy() {} + override fun destroy() { + // Stateless implementations have no key material to clear. + } + override fun isDestroyed() = false /** diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PacketMac.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PacketMac.kt index 3d8ce0c..6de80c7 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PacketMac.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PacketMac.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,10 @@ import javax.security.auth.Destroyable * Interface for SSH packet message authentication codes (MAC). */ internal interface PacketMac : Destroyable { - override fun destroy() {} + override fun destroy() { + // Stateless implementations have no key material to clear. + } + override fun isDestroyed() = false /** 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 3933891..9153480 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PrivateKeyReader.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/PrivateKeyReader.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,9 @@ package org.connectbot.sshlib.crypto import org.connectbot.sshlib.SshException +private const val OPENSSH_PRIVATE_KEY_BEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----" +private const val OPENSSH_PRIVATE_KEY_END = "-----END OPENSSH PRIVATE KEY-----" + internal object PrivateKeyReader { fun read(keyData: ByteArray, passphrase: String? = null): SshPrivateKey = read(String(keyData, Charsets.UTF_8), passphrase) @@ -26,8 +29,8 @@ internal object PrivateKeyReader { fun read(keyData: String, passphrase: String? = null): SshPrivateKey { val trimmed = keyData.trim() return when { - trimmed.startsWith("-----BEGIN OPENSSH PRIVATE KEY-----") -> { - val base64 = extractBase64(trimmed, "-----BEGIN OPENSSH PRIVATE KEY-----", "-----END OPENSSH PRIVATE KEY-----") + trimmed.startsWith(OPENSSH_PRIVATE_KEY_BEGIN) -> { + val base64 = extractBase64(trimmed, OPENSSH_PRIVATE_KEY_BEGIN, OPENSSH_PRIVATE_KEY_END) val data = Base64Compat.decode(base64) OpenSshKeyReader.read(data, passphrase) } @@ -47,8 +50,8 @@ internal object PrivateKeyReader { fun isEncrypted(keyData: String): Boolean { val trimmed = keyData.trim() return when { - trimmed.startsWith("-----BEGIN OPENSSH PRIVATE KEY-----") -> { - val base64 = extractBase64(trimmed, "-----BEGIN OPENSSH PRIVATE KEY-----", "-----END OPENSSH PRIVATE KEY-----") + trimmed.startsWith(OPENSSH_PRIVATE_KEY_BEGIN) -> { + val base64 = extractBase64(trimmed, OPENSSH_PRIVATE_KEY_BEGIN, OPENSSH_PRIVATE_KEY_END) val data = Base64Compat.decode(base64) OpenSshKeyReader.isEncrypted(data) } From fc2611a4edeffb5f211fa003c543536c967f0b4c Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Wed, 20 May 2026 18:39:20 -0700 Subject: [PATCH 2/6] chore: fix kotlin compilation warnings --- .../connectbot/sshlib/client/SshConnection.kt | 22 +++++++++---------- .../sshlib/crypto/ed25519/Ed25519Provider.kt | 7 +++--- 2 files changed, 14 insertions(+), 15 deletions(-) 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 77fc3e0..43dcdeb 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/SshConnection.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/SshConnection.kt @@ -604,11 +604,10 @@ class SshConnection( val sigEntry = SignatureEntry.fromSshName(sigAlgorithmName) ?: throw SshException("Unknown signature algorithm: $sigAlgorithmName") - val hostKeyBlob = serverHostKeyBlob - val useHostBound = serverAdvertisesHostBound && hostKeyBlob != null + val hostBoundKeyBlob = serverHostKeyBlob.takeIf { serverAdvertisesHostBound } - val signatureData = if (useHostBound && hostKeyBlob != null) { - buildHostBoundSignatureData(sid, username, SERVICE_SSH_CONNECTION, sigAlgorithmName, publicKeyBlob, hostKeyBlob) + val signatureData = if (hostBoundKeyBlob != null) { + buildHostBoundSignatureData(sid, username, SERVICE_SSH_CONNECTION, sigAlgorithmName, publicKeyBlob, hostBoundKeyBlob) } else { buildSignatureData(sid, username, SERVICE_SSH_CONNECTION, sigAlgorithmName, publicKeyBlob) } @@ -623,13 +622,13 @@ class SshConnection( setUserName(createAsciiString(username)) setServiceName(createAsciiString(SERVICE_SSH_CONNECTION)) - if (useHostBound && hostKeyBlob != null) { + if (hostBoundKeyBlob != null) { setMethodName(createAsciiString(METHOD_PUBLICKEY_HOSTBOUND)) val pubkeyAuth = UserauthRequestPublickeyHostbound().apply { setHasSignature(1) setPublicKeyAlgorithmName(createAsciiString(sigAlgorithmName)) setPublicKeyBlob(createByteString(publicKeyBlob)) - setServerHostKey(createByteString(hostKeyBlob)) + setServerHostKey(createByteString(hostBoundKeyBlob)) setSignature(createByteString(signature)) _check() } @@ -837,8 +836,7 @@ class SshConnection( channel: Channel, ): Boolean { val sid = sessionId ?: throw SshException(ERROR_SESSION_ID_NOT_ESTABLISHED) - val hostKeyBlob = serverHostKeyBlob - val useHostBound = serverAdvertisesHostBound && hostKeyBlob != null + val hostBoundKeyBlob = serverHostKeyBlob.takeIf { serverAdvertisesHostBound } val effectiveAlgorithmName = if (keyBlobAlgorithmName(key.publicKeyBlob) == KEY_TYPE_SSH_RSA) { negotiateRsaAlgorithm() @@ -846,8 +844,8 @@ class SshConnection( key.algorithmName } - val signatureData = if (useHostBound && hostKeyBlob != null) { - buildHostBoundSignatureData(sid, username, SERVICE_SSH_CONNECTION, effectiveAlgorithmName, key.publicKeyBlob, hostKeyBlob) + val signatureData = if (hostBoundKeyBlob != null) { + buildHostBoundSignatureData(sid, username, SERVICE_SSH_CONNECTION, effectiveAlgorithmName, key.publicKeyBlob, hostBoundKeyBlob) } else { buildSignatureData(sid, username, SERVICE_SSH_CONNECTION, effectiveAlgorithmName, key.publicKeyBlob) } @@ -855,13 +853,13 @@ class SshConnection( val signingKey = if (effectiveAlgorithmName != key.algorithmName) key.copy(algorithmName = effectiveAlgorithmName) else key val signature = handler.onSignatureRequest(signingKey, signatureData) ?: return false - if (useHostBound && hostKeyBlob != null) { + if (hostBoundKeyBlob != null) { sendAuthRequest(username, METHOD_PUBLICKEY_HOSTBOUND) { val pubkeyAuth = UserauthRequestPublickeyHostbound().apply { setHasSignature(1) setPublicKeyAlgorithmName(createAsciiString(effectiveAlgorithmName)) setPublicKeyBlob(createByteString(key.publicKeyBlob)) - setServerHostKey(createByteString(hostKeyBlob)) + setServerHostKey(createByteString(hostBoundKeyBlob)) setSignature(createByteString(signature)) _check() } 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 eaefe87..614f9de 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 @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ * limitations under the License. */ +@file:Suppress("DEPRECATION", "removal") + package org.connectbot.sshlib.crypto.ed25519 import java.security.AccessController @@ -22,10 +24,9 @@ import java.security.PrivilegedAction import java.security.Provider import java.security.Security -@Suppress("DEPRECATION", "removal") // Android only has the Provider(String, double, String) constructor +// Android only has the Provider(String, double, String) constructor. internal class Ed25519Provider : Provider(NAME, 1.0, "ConnectBot Ed25519 JCA Provider") { init { - @Suppress("DEPRECATION", "removal") AccessController.doPrivileged( PrivilegedAction { setup() From 55b88b0e392cdd6cbe35428aa8725de0a5b9eec2 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Wed, 20 May 2026 22:09:26 -0700 Subject: [PATCH 3/6] chore: add tests for protocol project --- protocol/build.gradle.kts | 13 ++++ .../sshlib/protocol/KaitaiUtilsTest.kt | 76 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 protocol/src/test/kotlin/org/connectbot/sshlib/protocol/KaitaiUtilsTest.kt diff --git a/protocol/build.gradle.kts b/protocol/build.gradle.kts index 0e9741d..73e2ff0 100644 --- a/protocol/build.gradle.kts +++ b/protocol/build.gradle.kts @@ -77,9 +77,22 @@ sourceSets { dependencies { api(libs.kaitai.runtime) implementation(kotlin("stdlib")) + testImplementation(kotlin("test")) kaitaiCompiler(libs.kaitai.compiler) } +tasks.test { + useJUnitPlatform() +} + +kover { + currentProject { + sources { + excludeJava.set(true) + } + } +} + java { withSourcesJar() toolchain { diff --git a/protocol/src/test/kotlin/org/connectbot/sshlib/protocol/KaitaiUtilsTest.kt b/protocol/src/test/kotlin/org/connectbot/sshlib/protocol/KaitaiUtilsTest.kt new file mode 100644 index 0000000..5f9cd89 --- /dev/null +++ b/protocol/src/test/kotlin/org/connectbot/sshlib/protocol/KaitaiUtilsTest.kt @@ -0,0 +1,76 @@ +/* + * ConnectBot SSH Library + * Copyright 2025-2026 Kenny Root + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.sshlib.protocol + +import io.kaitai.struct.KaitaiStream +import io.kaitai.struct.KaitaiStruct +import org.junit.jupiter.api.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class KaitaiUtilsTest { + + @Test + fun `toByteArray serializes generated Kaitai struct`() { + val message = ByteString().apply { + setLenData(3) + setData(byteArrayOf(1, 2, 3)) + } + + assertContentEquals(byteArrayOf(0, 0, 0, 3, 1, 2, 3), message.toByteArray()) + } + + @Test + fun `toByteArray grows buffer when initial capacity is too small`() { + val payloadSize = 20 * 1024 + val message = FixedPayloadStruct(payloadSize) + + val bytes = message.toByteArray() + + assertEquals(payloadSize, bytes.size) + assertEquals(0, bytes.first()) + assertEquals((payloadSize - 1).toByte(), bytes.last()) + } + + @Test + fun `toByteArray fails when message exceeds maximum serialization buffer`() { + val message = FixedPayloadStruct(1024 * 1024 + 1) + + val exception = assertFailsWith { + message.toByteArray() + } + assertEquals("Kaitai message exceeds 1048576 byte serialization limit", exception.message) + } + + private class FixedPayloadStruct( + private val payloadSize: Int, + ) : KaitaiStruct.ReadWrite(null) { + override fun _write_Seq() { + _io.writeBytes(ByteArray(payloadSize) { it.toByte() }) + } + + override fun _check() { + _dirty = false + } + + override fun _fetchInstances() = Unit + + override fun _read() = Unit + } +} From d735b315b4677fee79e2d353c4ea788bacebe85f Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Wed, 20 May 2026 22:49:06 -0700 Subject: [PATCH 4/6] chore: expand test coverage --- README.md | 4 +- docs/SK_AUTH.md | 2 +- sshlib/api.txt | 7 +- .../kotlin/org/connectbot/sshlib/SshClient.kt | 25 +- .../sshlib/blocking/BlockingSshClient.kt | 7 +- .../sshlib/client/sftp/SftpClientImpl.kt | 7 +- .../sshlib/client/sftp/SftpDispatcher.kt | 4 +- .../sshlib/client/sftp/SftpPacketIO.kt | 13 +- .../connectbot/sshlib/crypto/Base64Compat.kt | 9 +- sshlib/src/test/kotlin/android/util/Base64.kt | 44 ++ .../sshlib/ForwarderInterfacesTest.kt | 62 +++ .../org/connectbot/sshlib/SftpClientTest.kt | 161 ++++++ .../org/connectbot/sshlib/SshClientTest.kt | 357 ++++++++++++- .../sshlib/blocking/BlockingSshClientTest.kt | 106 +++- .../connectbot/sshlib/client/FakeSshServer.kt | 270 ++++++++++ .../sshlib/client/Socks5HandlerTest.kt | 68 ++- .../sshlib/client/SshConnectionFlowTest.kt | 495 ++++++++++++++++++ .../sshlib/client/sftp/SftpClientImplTest.kt | 449 ++++++++++++++++ .../sshlib/client/sftp/SftpDispatcherTest.kt | 231 ++++++++ .../sshlib/client/sftp/SftpPacketIOTest.kt | 141 +++++ .../sshlib/crypto/AdditionalCoverageTest.kt | 82 +++ .../sshlib/crypto/KeyEncryptionTest.kt | 83 +++ .../crypto/PacketPrimitiveInterfacesTest.kt | 79 +++ .../transport/TransportDependenciesTest.kt | 32 ++ 24 files changed, 2710 insertions(+), 28 deletions(-) create mode 100644 sshlib/src/test/kotlin/android/util/Base64.kt create mode 100644 sshlib/src/test/kotlin/org/connectbot/sshlib/ForwarderInterfacesTest.kt create mode 100644 sshlib/src/test/kotlin/org/connectbot/sshlib/SftpClientTest.kt create mode 100644 sshlib/src/test/kotlin/org/connectbot/sshlib/client/SshConnectionFlowTest.kt create mode 100644 sshlib/src/test/kotlin/org/connectbot/sshlib/client/sftp/SftpClientImplTest.kt create mode 100644 sshlib/src/test/kotlin/org/connectbot/sshlib/client/sftp/SftpDispatcherTest.kt create mode 100644 sshlib/src/test/kotlin/org/connectbot/sshlib/client/sftp/SftpPacketIOTest.kt create mode 100644 sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/AdditionalCoverageTest.kt create mode 100644 sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/KeyEncryptionTest.kt create mode 100644 sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/PacketPrimitiveInterfacesTest.kt create mode 100644 sshlib/src/test/kotlin/org/connectbot/sshlib/transport/TransportDependenciesTest.kt diff --git a/README.md b/README.md index 40ecbcc..cd41938 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ You can use it by running the following commands: ### Library API ```kotlin -val client = SshClient("example.com", 22) +val client = SshClient("example.com", port = 22, hostKeyVerifier = myVerifier) client.connect() client.authenticatePassword("user", "pass") @@ -146,7 +146,7 @@ class MyAgentProvider : AgentProvider { } // Enable agent forwarding -val client = SshClient("bastion.example.com") +val client = SshClient("bastion.example.com", hostKeyVerifier = myVerifier) client.connect() client.authenticatePassword("user", "pass") client.enableAgentForwarding(MyAgentProvider()) diff --git a/docs/SK_AUTH.md b/docs/SK_AUTH.md index f06dc4a..dbd12dd 100644 --- a/docs/SK_AUTH.md +++ b/docs/SK_AUTH.md @@ -48,7 +48,7 @@ class SkAuthHandler( override suspend fun onKeyboardInteractivePrompt(...) = null } -val client = SshClient("server.example.com") +val client = SshClient("server.example.com", hostKeyVerifier = myVerifier) client.connect() val result = client.authenticate("user", SkAuthHandler(...)) ``` diff --git a/sshlib/api.txt b/sshlib/api.txt index b58db7e..9fde503 100644 --- a/sshlib/api.txt +++ b/sshlib/api.txt @@ -73,6 +73,7 @@ package org.connectbot.sshlib { public interface AuthHandler { method public default suspend java.lang.Object? onAuthMethodsAvailable(java.util.Set methods, kotlin.coroutines.Continuation); + method public default suspend java.lang.Object? onBanner(java.lang.String message, kotlin.coroutines.Continuation); method public suspend java.lang.Object? onKeyboardInteractivePrompt(java.lang.String name, java.lang.String instruction, java.util.List prompts, kotlin.coroutines.Continuation?>); method public suspend java.lang.Object? onPasswordNeeded(kotlin.coroutines.Continuation); method public suspend java.lang.Object? onPublicKeysNeeded(kotlin.coroutines.Continuation>); @@ -546,9 +547,9 @@ package org.connectbot.sshlib { } public static final class SshClient.Companion { - method public operator org.connectbot.sshlib.SshClient invoke(java.lang.String host, optional int port, optional java.lang.String clientVersion); + method public operator org.connectbot.sshlib.SshClient invoke(java.lang.String host, org.connectbot.sshlib.HostKeyVerifier hostKeyVerifier, optional int port, optional java.lang.String clientVersion); method public operator org.connectbot.sshlib.SshClient invoke(org.connectbot.sshlib.SshClientConfig config); - method public operator org.connectbot.sshlib.SshClient invoke(org.connectbot.sshlib.transport.TransportFactory transportFactory, optional java.lang.String clientVersion); + method public operator org.connectbot.sshlib.SshClient invoke(org.connectbot.sshlib.transport.TransportFactory transportFactory, org.connectbot.sshlib.HostKeyVerifier hostKeyVerifier, optional java.lang.String clientVersion); } public final class SshClientConfig { @@ -693,7 +694,7 @@ package org.connectbot.sshlib.blocking { ctor public BlockingSshClient(java.lang.String host, optional int port, org.connectbot.sshlib.HostKeyVerifier hostKeyVerifier, optional java.lang.String clientVersion); ctor public BlockingSshClient(java.lang.String host, org.connectbot.sshlib.HostKeyVerifier hostKeyVerifier); ctor public BlockingSshClient(org.connectbot.sshlib.SshClientConfig config); - ctor public BlockingSshClient(org.connectbot.sshlib.transport.TransportFactory transportFactory, optional java.lang.String clientVersion); + ctor public BlockingSshClient(org.connectbot.sshlib.transport.TransportFactory transportFactory, org.connectbot.sshlib.HostKeyVerifier hostKeyVerifier, optional java.lang.String clientVersion); method @kotlin.jvm.Throws(exceptionClasses=SshException::class) public void authenticate(java.lang.String username, org.connectbot.sshlib.AuthHandler handler) throws org.connectbot.sshlib.SshException; method @kotlin.jvm.Throws(exceptionClasses=SshException::class) public void authenticateKeyboardInteractive(java.lang.String username, org.connectbot.sshlib.KeyboardInteractiveCallback callback) throws org.connectbot.sshlib.SshException; method @kotlin.jvm.Throws(exceptionClasses=SshException::class) public void authenticatePassword(java.lang.String username, java.lang.String password) throws org.connectbot.sshlib.SshException; diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClient.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClient.kt index 3fa47d5..7353d9e 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClient.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/SshClient.kt @@ -48,7 +48,7 @@ import java.net.InetSocketAddress * * Usage with TCP (default): * ```kotlin - * val client = SshClient("example.com") + * val client = SshClient("example.com", hostKeyVerifier = myVerifier) * if (client.connect() is ConnectResult.Success) { * if (client.authenticatePassword("user", "password") is AuthResult.Success) { * val session = client.openSession() @@ -75,6 +75,9 @@ import java.net.InetSocketAddress */ class SshClient private constructor( private val config: SshClientConfig, + initialTransport: Transport? = null, + initialConnection: SshConnection? = null, + initialAuthenticated: Boolean = false, ) { companion object { private val logger = LoggerFactory.getLogger(SshClient::class.java) @@ -92,12 +95,14 @@ class SshClient private constructor( */ operator fun invoke( host: String, + hostKeyVerifier: HostKeyVerifier, port: Int = 22, clientVersion: String = "SSH-2.0-CBSSH_1.0", ): SshClient { val config = SshClientConfig { this.host = host this.port = port + this.hostKeyVerifier = hostKeyVerifier this.clientVersion = clientVersion } return SshClient(config) @@ -116,19 +121,29 @@ class SshClient private constructor( */ operator fun invoke( transportFactory: TransportFactory, + hostKeyVerifier: HostKeyVerifier, clientVersion: String = "SSH-2.0-CBSSH_1.0", ): SshClient { val config = SshClientConfig { this.transportFactory = transportFactory + this.hostKeyVerifier = hostKeyVerifier this.clientVersion = clientVersion } return SshClient(config) } + + @JvmSynthetic + internal fun createForTesting( + config: SshClientConfig, + initialTransport: Transport? = null, + initialConnection: SshConnection? = null, + initialAuthenticated: Boolean = false, + ): SshClient = SshClient(config, initialTransport, initialConnection, initialAuthenticated) } - private var transport: Transport? = null - private var connection: SshConnection? = null - private var authenticated = false + private var transport: Transport? = initialTransport + private var connection: SshConnection? = initialConnection + private var authenticated = initialAuthenticated private val forwardingScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private var disconnectForwardJob: Job? = null @@ -618,7 +633,7 @@ class SshClient private constructor( * ProxyJump pattern). * * ```kotlin - * val jump = SshClient("jump.example.com") + * val jump = SshClient("jump.example.com", hostKeyVerifier = jumpVerifier) * jump.connect() * jump.authenticatePassword("user", "pass") * 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 5568169..0407389 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/blocking/BlockingSshClient.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/blocking/BlockingSshClient.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,7 +65,7 @@ import java.net.InetSocketAddress * * With custom transport: * ```java - * BlockingSshClient client = new BlockingSshClient(myTransportFactory); + * BlockingSshClient client = new BlockingSshClient(myTransportFactory, myHostKeyVerifier); * ``` */ class BlockingSshClient internal constructor( @@ -99,8 +99,9 @@ class BlockingSshClient internal constructor( */ constructor( transportFactory: TransportFactory, + hostKeyVerifier: HostKeyVerifier, clientVersion: String = "SSH-2.0-CBSSH_1.0", - ) : this(SshClient(transportFactory, clientVersion)) + ) : this(SshClient(transportFactory, hostKeyVerifier, clientVersion)) /** * Connect to the SSH server and perform key exchange. diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/sftp/SftpClientImpl.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/sftp/SftpClientImpl.kt index 2bda424..6d267e8 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/sftp/SftpClientImpl.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/sftp/SftpClientImpl.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -385,8 +385,9 @@ internal class SftpClientImpl private constructor( /** * Create an SFTP client by performing the INIT/VERSION handshake. */ - suspend fun create(session: SshSession): SftpResult { - val packetIO = SftpPacketIO(session) + suspend fun create(session: SshSession): SftpResult = create(session, SftpPacketIO(session)) + + internal suspend fun create(session: SshSession, packetIO: SftpPacketTransport): SftpResult { val dispatcher = SftpDispatcher(packetIO) // Send SSH_FXP_INIT diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/sftp/SftpDispatcher.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/sftp/SftpDispatcher.kt index 7c53a0b..3c95c24 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/sftp/SftpDispatcher.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/sftp/SftpDispatcher.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ import java.util.concurrent.atomic.AtomicInteger * Each outbound request gets a unique request ID. A background read loop * parses incoming SFTP packets and completes the matching deferred. */ -internal class SftpDispatcher(private val packetIO: SftpPacketIO) { +internal class SftpDispatcher(private val packetIO: SftpPacketTransport) { private val nextRequestId = AtomicInteger(1) private val pending = ConcurrentHashMap>() private val writeMutex = Mutex() diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/sftp/SftpPacketIO.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/sftp/SftpPacketIO.kt index e007076..7021c7f 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/client/sftp/SftpPacketIO.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/client/sftp/SftpPacketIO.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,12 @@ import java.nio.ByteBuffer * packet boundaries. This class accumulates bytes until a complete packet * is available. */ -internal class SftpPacketIO(private val session: SshSession) { +internal interface SftpPacketTransport { + suspend fun readPacket(): SftpResult + suspend fun writePacket(type: Int, payload: ByteArray): SftpResult +} + +internal class SftpPacketIO(private val session: SshSession) : SftpPacketTransport { private val buffer = ByteArrayOutputStream() private var bufferedBytes = ByteArray(0) private var bufferedOffset = 0 @@ -46,7 +51,7 @@ internal class SftpPacketIO(private val session: SshSession) { * explicitly. (Reviewed by @kruton on PR #112: Kotlin library APIs * shouldn't throw for things they can manage themselves.) */ - suspend fun readPacket(): SftpResult { + override suspend fun readPacket(): SftpResult { return try { // Read the 4-byte length prefix val lengthBytes = readExact(4) @@ -73,7 +78,7 @@ internal class SftpPacketIO(private val session: SshSession) { * Returns [SftpResult.Success] on send or [SftpResult.IoError] if the * underlying SSH session write fails. */ - suspend fun writePacket(type: Int, payload: ByteArray): SftpResult = try { + override suspend fun writePacket(type: Int, payload: ByteArray): SftpResult = try { val length = 1 + payload.size // type byte + payload val packet = ByteBuffer.allocate(4 + length) packet.putInt(length) diff --git a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Base64Compat.kt b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Base64Compat.kt index 564ecab..3af5155 100644 --- a/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Base64Compat.kt +++ b/sshlib/src/main/kotlin/org/connectbot/sshlib/crypto/Base64Compat.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.connectbot.sshlib.crypto +import java.lang.reflect.InvocationTargetException import java.util.Base64 /** @@ -70,6 +71,10 @@ internal object Base64Compat { override fun encodeWithPadding(data: ByteArray): String = encodeMethod.invoke(null, data, NO_WRAP) as String - override fun decode(data: String): ByteArray = decodeMethod.invoke(null, data, DEFAULT) as ByteArray + override fun decode(data: String): ByteArray = try { + decodeMethod.invoke(null, data, DEFAULT) as ByteArray + } catch (e: InvocationTargetException) { + throw e.targetException + } } } diff --git a/sshlib/src/test/kotlin/android/util/Base64.kt b/sshlib/src/test/kotlin/android/util/Base64.kt new file mode 100644 index 0000000..6593d0c --- /dev/null +++ b/sshlib/src/test/kotlin/android/util/Base64.kt @@ -0,0 +1,44 @@ +/* + * ConnectBot SSH Library + * Copyright 2025-2026 Kenny Root + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util + +class Base64 { + companion object { + @JvmField + val NO_WRAP: Int = 2 + + @JvmField + val NO_PADDING: Int = 1 + + @JvmField + val DEFAULT: Int = 0 + + @JvmStatic + fun encodeToString(data: ByteArray, flags: Int): String { + val encoder = if ((flags and NO_PADDING) != 0) { + java.util.Base64.getEncoder().withoutPadding() + } else { + java.util.Base64.getEncoder() + } + return encoder.encodeToString(data) + } + + @JvmStatic + fun decode(data: String, flags: Int): ByteArray = java.util.Base64.getDecoder().decode(data) + } +} diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/ForwarderInterfacesTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/ForwarderInterfacesTest.kt new file mode 100644 index 0000000..38c7316 --- /dev/null +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/ForwarderInterfacesTest.kt @@ -0,0 +1,62 @@ +/* + * ConnectBot SSH Library + * Copyright 2025-2026 Kenny Root + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.sshlib + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class ForwarderInterfacesTest { + + @Test + fun `PortForwarder close delegates to stop`() { + val forwarder = TestPortForwarder() + + forwarder.close() + + assertEquals(1, forwarder.stopCalls) + } + + @Test + fun `StreamForwarder close delegates to stop`() { + val forwarder = TestStreamForwarder() + + forwarder.close() + + assertEquals(1, forwarder.stopCalls) + } + + private class TestPortForwarder : PortForwarder { + override val boundHost: String = "127.0.0.1" + override val boundPort: Int = 8022 + override val isActive: Boolean = true + var stopCalls = 0 + + override suspend fun stop() { + stopCalls++ + } + } + + private class TestStreamForwarder : StreamForwarder { + override val isActive: Boolean = true + var stopCalls = 0 + + override suspend fun stop() { + stopCalls++ + } + } +} diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/SftpClientTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/SftpClientTest.kt new file mode 100644 index 0000000..02ac70a --- /dev/null +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/SftpClientTest.kt @@ -0,0 +1,161 @@ +/* + * ConnectBot SSH Library + * Copyright 2025-2026 Kenny Root + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.sshlib + +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class SftpClientTest { + + @Test + fun `listdir accumulates batches and closes directory handle`() = runBlocking { + val first = SftpDirectoryEntry("one", "one", SftpAttributes(size = 1)) + val second = SftpDirectoryEntry("two", "two", SftpAttributes(size = 2)) + val client = FakeSftpClient( + readResults = ArrayDeque( + listOf( + SftpResult.Success(listOf(first)), + SftpResult.Success(listOf(second)), + SftpResult.Success(null), + ), + ), + ) + + val result = client.listdir("/tmp") + + assertEquals(SftpResult.Success(listOf(first, second)), result) + assertEquals(listOf("/tmp"), client.openedPaths) + assertEquals(listOf(client.handle), client.closedHandles) + } + + @Test + fun `listdir returns opendir server errors without closing unopened handle`() = runBlocking { + val error = SftpResult.ServerError(SftpStatusCode.NO_SUCH_FILE, "missing") + val client = FakeSftpClient(openResult = error) + + assertEquals(error, client.listdir("/missing")) + assertTrue(client.closedHandles.isEmpty()) + } + + @Test + fun `listdir returns opendir protocol errors`() = runBlocking { + val error = SftpResult.ProtocolError("bad handle") + val client = FakeSftpClient(openResult = error) + + assertEquals(error, client.listdir("/bad")) + assertTrue(client.closedHandles.isEmpty()) + } + + @Test + fun `listdir returns opendir io errors`() = runBlocking { + val error = SftpResult.IoError(IllegalStateException("closed")) + val client = FakeSftpClient(openResult = error) + + assertEquals(error, client.listdir("/io")) + assertTrue(client.closedHandles.isEmpty()) + } + + @Test + fun `listdir returns readdir server errors and closes handle`() = runBlocking { + val error = SftpResult.ServerError(SftpStatusCode.PERMISSION_DENIED, "denied") + val client = FakeSftpClient(readResults = ArrayDeque(listOf(error))) + + assertEquals(error, client.listdir("/denied")) + assertEquals(listOf(client.handle), client.closedHandles) + } + + @Test + fun `listdir returns readdir protocol errors and closes handle`() = runBlocking { + val error = SftpResult.ProtocolError("unexpected packet") + val client = FakeSftpClient(readResults = ArrayDeque(listOf(error))) + + assertEquals(error, client.listdir("/protocol")) + assertEquals(listOf(client.handle), client.closedHandles) + } + + @Test + fun `listdir returns readdir io errors and closes handle`() = runBlocking { + val error = SftpResult.IoError(IllegalStateException("read failed")) + val client = FakeSftpClient(readResults = ArrayDeque(listOf(error))) + + assertEquals(error, client.listdir("/io")) + assertEquals(listOf(client.handle), client.closedHandles) + } + + private class FakeSftpClient( + private val openResult: SftpResult = SftpResult.Success(SftpFileHandle(byteArrayOf(1, 2, 3))), + private val readResults: ArrayDeque?>> = ArrayDeque(), + ) : SftpClient { + val handle = (openResult as? SftpResult.Success)?.value ?: SftpFileHandle(byteArrayOf(9)) + val openedPaths = mutableListOf() + val closedHandles = mutableListOf() + + override val protocolVersion: Int = 3 + override val isOpen: Boolean = true + + override suspend fun open( + path: String, + flags: Set, + attrs: SftpAttributes, + ): SftpResult = throw UnsupportedOperationException() + + override suspend fun close(handle: SftpFileHandle): SftpResult { + closedHandles += handle + return SftpResult.Success(Unit) + } + + override suspend fun read(handle: SftpFileHandle, offset: Long, length: Int): SftpResult = throw UnsupportedOperationException() + + override suspend fun write(handle: SftpFileHandle, offset: Long, data: ByteArray): SftpResult = throw UnsupportedOperationException() + + override suspend fun stat(path: String): SftpResult = throw UnsupportedOperationException() + + override suspend fun lstat(path: String): SftpResult = throw UnsupportedOperationException() + + override suspend fun fstat(handle: SftpFileHandle): SftpResult = throw UnsupportedOperationException() + + override suspend fun setstat(path: String, attrs: SftpAttributes): SftpResult = throw UnsupportedOperationException() + + override suspend fun fsetstat(handle: SftpFileHandle, attrs: SftpAttributes): SftpResult = throw UnsupportedOperationException() + + override suspend fun opendir(path: String): SftpResult { + openedPaths += path + return openResult + } + + override suspend fun readdir(handle: SftpFileHandle): SftpResult?> = readResults.removeFirst() + + override suspend fun mkdir(path: String, attrs: SftpAttributes): SftpResult = throw UnsupportedOperationException() + + override suspend fun rmdir(path: String): SftpResult = throw UnsupportedOperationException() + + override suspend fun remove(path: String): SftpResult = throw UnsupportedOperationException() + + override suspend fun rename(oldPath: String, newPath: String): SftpResult = throw UnsupportedOperationException() + + override suspend fun realpath(path: String): SftpResult = throw UnsupportedOperationException() + + override suspend fun readlink(path: String): SftpResult = throw UnsupportedOperationException() + + override suspend fun symlink(targetPath: String, linkPath: String): SftpResult = throw UnsupportedOperationException() + + override fun close() = Unit + } +} diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/SshClientTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/SshClientTest.kt index c38c00a..a708a30 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/SshClientTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/SshClientTest.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,14 +17,30 @@ package org.connectbot.sshlib +import io.ktor.utils.io.ByteChannel +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify import kotlinx.coroutines.test.runTest import nl.jqno.equalsverifier.EqualsVerifier +import org.connectbot.sshlib.client.ForwardingChannel +import org.connectbot.sshlib.client.SessionChannel +import org.connectbot.sshlib.client.SshConnection +import org.connectbot.sshlib.crypto.SshPrivateKey +import org.connectbot.sshlib.transport.Transport import org.connectbot.sshlib.transport.TransportFactory import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import java.net.InetSocketAddress +import java.nio.file.Files +import java.nio.file.Paths import kotlin.test.assertFailsWith +import kotlin.test.assertFalse import kotlin.test.assertIs import kotlin.test.assertNull +import kotlin.test.assertTrue class SshClientTest { private val acceptAllVerifier = object : HostKeyVerifier { @@ -80,6 +96,16 @@ class SshClientTest { assertIs(result) } + @Test + fun `host factory overload accepts host verifier`() { + SshClient("example.com", hostKeyVerifier = acceptAllVerifier) + } + + @Test + fun `transport factory overload accepts host verifier`() { + SshClient(TransportFactory { throw Exception("unused") }, acceptAllVerifier) + } + @Test fun `openSession returns null when not connected`() = runTest { val client = clientWithHost("host") @@ -115,6 +141,60 @@ class SshClientTest { assertIs(result) } + @Test + fun `authenticate wrappers set authenticated on success and preserve failures`() = runTest { + val connection = mockk(relaxed = true) + val client = connectedClient(connection) + coEvery { connection.authenticatePassword("user", "pass") } returns AuthResult.Success + coEvery { connection.authenticateKeyboardInteractive("user", mockKeyboardInteractiveCallback) } returns AuthResult.Failure(setOf("password")) + coEvery { connection.authenticate("user", mockAuthHandler) } returns AuthResult.Success + + assertEquals(AuthResult.Success, client.authenticatePassword("user", "pass")) + assertTrue(client.isAuthenticated) + assertEquals(AuthResult.Failure(setOf("password")), client.authenticateKeyboardInteractive("user", mockKeyboardInteractiveCallback)) + assertEquals(AuthResult.Success, client.authenticate("user", mockAuthHandler)) + } + + @Test + fun `authenticate wrappers convert connection exceptions to errors`() = runTest { + val connection = mockk(relaxed = true) + val client = connectedClient(connection) + coEvery { connection.authenticatePassword(any(), any()) } throws IllegalStateException("password failed") + coEvery { connection.authenticateKeyboardInteractive(any(), any()) } throws IllegalStateException("keyboard failed") + coEvery { connection.authenticate(any(), any()) } throws IllegalStateException("handler failed") + + assertAuthError("password failed", client.authenticatePassword("user", "pass")) + assertAuthError("keyboard failed", client.authenticateKeyboardInteractive("user", mockKeyboardInteractiveCallback)) + assertAuthError("handler failed", client.authenticate("user", mockAuthHandler)) + } + + @Test + fun `authenticatePublicKey string and byte array overloads delegate parsed keys`() = runTest { + val keyData = Files.readString(Paths.get("src/test/resources/keys/ed25519_unencrypted")) + val connection = mockk(relaxed = true) + val client = connectedClient(connection) + coEvery { connection.authenticatePublicKey(eq("user"), any()) } returns AuthResult.Success + + assertEquals(AuthResult.Success, client.authenticatePublicKey("user", keyData)) + assertTrue(client.isAuthenticated) + assertEquals(AuthResult.Success, client.authenticatePublicKey("user", keyData.toByteArray())) + + coVerify(exactly = 2) { connection.authenticatePublicKey(eq("user"), any()) } + } + + @Test + fun `authenticatePublicKey preserves failures and maps parse errors`() = runTest { + val keyData = Files.readString(Paths.get("src/test/resources/keys/ed25519_unencrypted")) + val connection = mockk(relaxed = true) + val client = connectedClient(connection) + val failure = AuthResult.Failure(setOf("password")) + coEvery { connection.authenticatePublicKey(eq("user"), any()) } returns failure + + assertEquals(failure, client.authenticatePublicKey("user", keyData)) + assertFalse(client.isAuthenticated) + assertIs(client.authenticatePublicKey("user", "not a private key")) + } + @Test fun `isPrivateKeyEncrypted handles invalid key data gracefully`() { val client = clientWithHost("host") @@ -128,6 +208,37 @@ class SshClientTest { assertNull(client.connectionInfo) } + @Test + fun `connectionInfo delegates to active connection`() { + val connection = mockk(relaxed = true) + val info = ConnectionInfo( + kexAlgorithm = "curve25519-sha256", + serverHostKeyAlgorithm = "ssh-ed25519", + encryptionAlgorithmC2S = "aes128-ctr", + encryptionAlgorithmS2C = "aes128-ctr", + macAlgorithmC2S = "hmac-sha2-256", + macAlgorithmS2C = "hmac-sha2-256", + ) + every { connection.connectionInfo } returns info + + assertEquals(info, connectedClient(connection).connectionInfo) + } + + @Test + fun `isAuthenticated is false before connect`() { + val client = clientWithHost("host") + assertFalse(client.isAuthenticated) + } + + @Test + fun `isAuthenticated requires connection authenticated flag and connected transport`() { + val connection = mockk(relaxed = true) + + assertTrue(connectedClient(connection, authenticated = true, transportConnected = true).isAuthenticated) + assertFalse(connectedClient(connection, authenticated = true, transportConnected = false).isAuthenticated) + assertFalse(connectedClient(connection, authenticated = false, transportConnected = true).isAuthenticated) + } + @Test fun `SshClientConfig has default rekey thresholds`() { val config = SshClientConfig { @@ -223,4 +334,248 @@ class SshClientTest { } } } + + @Test + fun `openSftp returns io error when not authenticated`() = runTest { + val client = clientWithHost("host") + + assertIs(client.openSftp()) + } + + @Test + fun `openSession delegates and handles connection exceptions`() = runTest { + val connection = mockk(relaxed = true) + val session = mockk() + val client = connectedClient(connection, authenticated = true) + coEvery { connection.openSessionChannel() } returns session + assertEquals(session, client.openSession()) + + coEvery { connection.openSessionChannel() } throws IllegalStateException("open failed") + assertNull(client.openSession()) + } + + @Test + fun `openSftp maps session open and subsystem failures`() = runTest { + val connection = mockk(relaxed = true) + val client = connectedClient(connection, authenticated = true) + coEvery { connection.openSessionChannel() } returns null + assertIs(client.openSftp()) + + val session = mockk(relaxed = true) + coEvery { connection.openSessionChannel() } returns session + coEvery { session.requestSubsystem("sftp") } returns false + assertIs(client.openSftp()) + verify { session.close() } + + coEvery { connection.openSessionChannel() } throws IllegalStateException("boom") + assertIs(client.openSftp()) + } + + @Test + fun `localPortForward returns null when not authenticated`() = runTest { + val client = clientWithHost("host") + + assertNull(client.localPortForward(InetSocketAddress("127.0.0.1", 0), "remote", 22)) + assertNull(client.localPortForward(0, "remote", 22)) + } + + @Test + fun `localPortForward creates and stops loopback listener when authenticated`() = runTest { + val connection = mockk(relaxed = true) + val forwarder = connectedClient(connection, authenticated = true) + .localPortForward(InetSocketAddress("127.0.0.1", 0), "remote", 22) + + assertTrue(forwarder!!.isActive) + assertTrue(forwarder.boundPort > 0) + forwarder.stop() + assertFalse(forwarder.isActive) + } + + @Test + fun `remotePortForward returns null when not authenticated`() = runTest { + val client = clientWithHost("host") + + assertNull(client.remotePortForward("127.0.0.1", 0, "localhost", 22)) + } + + @Test + fun `remotePortForward maps server rejection and creates forwarder`() = runTest { + val connection = mockk(relaxed = true) + coEvery { connection.sendTcpipForwardRequest("127.0.0.1", 0) } returns null + assertNull(connectedClient(connection, authenticated = true).remotePortForward("127.0.0.1", 0, "localhost", 22)) + + coEvery { connection.sendTcpipForwardRequest("127.0.0.1", 0) } returns 8022 + val forwarder = connectedClient(connection, authenticated = true) + .remotePortForward("127.0.0.1", 0, "localhost", 22) + + assertTrue(forwarder!!.isActive) + assertEquals(8022, forwarder.boundPort) + forwarder.stop() + assertFalse(forwarder.isActive) + verify { connection.registerRemoteForwarder(eq("127.0.0.1:8022"), any()) } + coVerify { connection.sendCancelTcpipForward("127.0.0.1", 8022) } + } + + @Test + fun `dynamicPortForward returns null when not authenticated`() = runTest { + val client = clientWithHost("host") + + assertNull(client.dynamicPortForward(InetSocketAddress("127.0.0.1", 0))) + assertNull(client.dynamicPortForward(0)) + } + + @Test + fun `dynamicPortForward creates and stops loopback listener when authenticated`() = runTest { + val connection = mockk(relaxed = true) + val forwarder = connectedClient(connection, authenticated = true) + .dynamicPortForward(InetSocketAddress("127.0.0.1", 0)) + + assertTrue(forwarder!!.isActive) + assertTrue(forwarder.boundPort > 0) + forwarder.stop() + assertFalse(forwarder.isActive) + } + + @Test + fun `forwardStream returns null when not authenticated`() = runTest { + val client = clientWithHost("host") + val readChannel = ByteChannel(autoFlush = true) + val writeChannel = ByteChannel(autoFlush = true) + + assertNull(client.forwardStream(readChannel, writeChannel, "remote", 22)) + } + + @Test + fun `forwardStream maps direct channel failure and success`() = runTest { + val connection = mockk(relaxed = true) + val client = connectedClient(connection, authenticated = true) + coEvery { connection.openDirectTcpipChannel("remote", 22, "127.0.0.1", 0) } returns null + + assertNull(client.forwardStream(ByteChannel(autoFlush = true), ByteChannel(autoFlush = true), "remote", 22)) + + val channel = ForwardingChannel(connection, 1, 2, 32768, 32768) + coEvery { connection.openDirectTcpipChannel("remote", 22, "127.0.0.1", 0) } returns channel + val forwarder = client.forwardStream(ByteChannel(autoFlush = true), ByteChannel(autoFlush = true), "remote", 22) + + assertTrue(forwarder!!.isActive) + forwarder.stop() + assertFalse(forwarder.isActive) + } + + @Test + fun `openDirectTcpipTransport returns null when not authenticated`() { + val client = clientWithHost("host") + + assertNull(client.openDirectTcpipTransport("remote", 22)) + } + + @Test + fun `openDirectTcpipTransport opens direct tcpip channel from factory`() = runTest { + val connection = mockk(relaxed = true) + val channel = ForwardingChannel(connection, 1, 2, 32768, 32768) + coEvery { connection.openDirectTcpipChannel("remote", 22, "127.0.0.1", 0) } returns channel + + val factory = connectedClient(connection, authenticated = true) + .openDirectTcpipTransport("remote", 22) + + assertTrue(factory!!.create().isConnected) + } + + @Test + fun `openDirectTcpipTransport factory throws when channel open fails`() = runTest { + val connection = mockk(relaxed = true) + coEvery { connection.openDirectTcpipChannel(any(), any(), any(), any()) } returns null + + val factory = connectedClient(connection, authenticated = true) + .openDirectTcpipTransport("remote", 22) + + assertFailsWith { + factory!!.create() + } + } + + @Test + fun `ping returns not authenticated before connect`() = runTest { + val client = clientWithHost("host") + + assertEquals(PingResult.NotAuthenticated, client.ping()) + } + + @Test + fun `ping delegates after authentication`() = runTest { + val connection = mockk(relaxed = true) + coEvery { connection.ping() } returns PingResult.NotSupported + + assertEquals(PingResult.NotSupported, connectedClient(connection, authenticated = true).ping()) + } + + @Test + fun `disconnect is idempotent before connect`() = runTest { + val client = clientWithHost("host") + + client.disconnect() + + assertFalse(client.isAuthenticated) + assertNull(client.connectionInfo) + } + + @Test + fun `disconnect closes connection and transport and clears authentication`() = runTest { + val connection = mockk(relaxed = true) + val transport = FakeTransport() + val client = connectedClient(connection, authenticated = true, transport = transport) + + client.disconnect() + + coVerify { connection.close() } + assertEquals(1, transport.closeCalls) + assertFalse(client.isAuthenticated) + assertNull(client.connectionInfo) + } + + @Test + fun `enableAgentForwarding delegates when connected`() { + val connection = mockk(relaxed = true) + val provider = mockk() + + connectedClient(connection).enableAgentForwarding(provider) + + verify { connection.enableAgentForwarding(provider) } + } + + private fun connectedClient( + connection: SshConnection, + authenticated: Boolean = false, + transportConnected: Boolean = true, + transport: FakeTransport = FakeTransport(transportConnected), + ): SshClient { + val config = SshClientConfig { + host = "host" + hostKeyVerifier = acceptAllVerifier + } + return SshClient.createForTesting(config, transport, connection, authenticated) + } + + private fun assertAuthError(message: String, result: AuthResult) { + val error = assertIs(result) + assertEquals(message, error.message) + } + + private class FakeTransport( + private var connected: Boolean = true, + ) : Transport { + var closeCalls = 0 + + override val isConnected: Boolean + get() = connected + + override suspend fun read(count: Int): ByteArray = throw UnsupportedOperationException() + + override suspend fun write(data: ByteArray) = Unit + + override suspend fun close() { + closeCalls++ + connected = false + } + } } diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/blocking/BlockingSshClientTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/blocking/BlockingSshClientTest.kt index 797f926..d968e57 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/blocking/BlockingSshClientTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/blocking/BlockingSshClientTest.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,23 +17,34 @@ package org.connectbot.sshlib.blocking +import io.ktor.utils.io.ByteChannel import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.mockk import org.connectbot.sshlib.AuthResult import org.connectbot.sshlib.ConnectResult import org.connectbot.sshlib.HostKeyVerifier +import org.connectbot.sshlib.PingResult +import org.connectbot.sshlib.PortForwarder import org.connectbot.sshlib.PublicKey +import org.connectbot.sshlib.SftpClient +import org.connectbot.sshlib.SftpResult import org.connectbot.sshlib.SshClient import org.connectbot.sshlib.SshClientConfig import org.connectbot.sshlib.SshException +import org.connectbot.sshlib.SshSession +import org.connectbot.sshlib.StreamForwarder import org.connectbot.sshlib.transport.ByteArrayTransport import org.connectbot.sshlib.transport.Transport import org.connectbot.sshlib.transport.TransportException import org.connectbot.sshlib.transport.TransportFactory import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertSame import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import java.net.InetSocketAddress class BlockingSshClientTest { @@ -89,6 +100,27 @@ class BlockingSshClientTest { assertEquals(cause, ex.cause) } + @Test + fun `connect returns normally on success`() { + val client = mockk(relaxed = true) + coEvery { client.connect() } returns ConnectResult.Success + + BlockingSshClient(client).connect() + } + + @Test + fun `connect handles TransportError`() { + val client = mockk(relaxed = true) + val cause = Exception("transport") + coEvery { client.connect() } returns ConnectResult.TransportError(cause) + + val ex = assertThrows { + BlockingSshClient(client).connect() + } + assertTrue(ex.message!!.contains("Transport error")) + assertSame(cause, ex.cause) + } + @Test fun `authenticatePassword handles Failure`() { val client = mockk(relaxed = true) @@ -116,6 +148,78 @@ class BlockingSshClientTest { assertEquals(cause, ex.cause) } + @Test + fun `authenticate wrappers return normally on success`() { + val client = mockk(relaxed = true) + coEvery { client.authenticatePassword(any(), any()) } returns AuthResult.Success + coEvery { client.authenticate(any(), any()) } returns AuthResult.Success + coEvery { client.authenticateKeyboardInteractive(any(), any()) } returns AuthResult.Success + coEvery { client.authenticatePublicKey(any(), any(), any()) } returns AuthResult.Success + coEvery { client.authenticatePublicKey(any(), any(), any()) } returns AuthResult.Success + val blockingClient = BlockingSshClient(client) + + blockingClient.authenticatePassword("user", "pass") + blockingClient.authenticate("user", mockk()) + blockingClient.authenticateKeyboardInteractive("user", mockk()) + blockingClient.authenticatePublicKey("user", byteArrayOf(1, 2, 3)) + blockingClient.authenticatePublicKey("user", "key") + } + + @Test + fun `authenticate wrappers throw on failure and error variants`() { + val client = mockk(relaxed = true) + val cause = Exception("auth") + coEvery { client.authenticate(any(), any()) } returns AuthResult.Failure(setOf("password")) + coEvery { client.authenticateKeyboardInteractive(any(), any()) } returns AuthResult.Error("keyboard error", cause) + coEvery { client.authenticatePublicKey(any(), any(), any()) } returns AuthResult.Failure(setOf("publickey")) + coEvery { client.authenticatePublicKey(any(), any(), any()) } returns AuthResult.Error("key error", cause) + val blockingClient = BlockingSshClient(client) + + assertThrows { blockingClient.authenticate("user", mockk()) } + assertThrows { blockingClient.authenticateKeyboardInteractive("user", mockk()) } + assertThrows { blockingClient.authenticatePublicKey("user", byteArrayOf(1, 2, 3)) } + assertThrows { blockingClient.authenticatePublicKey("user", "key") } + } + + @Test + fun `simple wrappers delegate to async client`() { + val client = mockk(relaxed = true) + val session = mockk() + val sftp = mockk() + val localForwarder = mockk() + val remoteForwarder = mockk() + val dynamicForwarder = mockk() + val streamForwarder = mockk() + val readChannel = ByteChannel(autoFlush = true) + val writeChannel = ByteChannel(autoFlush = true) + val bindAddress = InetSocketAddress("127.0.0.1", 0) + coEvery { client.openSession() } returns session + coEvery { client.openSftp() } returns SftpResult.Success(sftp) + coEvery { client.ping() } returns PingResult.NotSupported + coEvery { client.localPortForward(bindAddress, "remote", 22) } returns localForwarder + coEvery { client.localPortForward(8022, "remote", 22) } returns localForwarder + coEvery { client.remotePortForward("0.0.0.0", 0, "localhost", 22) } returns remoteForwarder + coEvery { client.dynamicPortForward(bindAddress, null) } returns dynamicForwarder + coEvery { client.dynamicPortForward(1080, null) } returns dynamicForwarder + coEvery { client.forwardStream(readChannel, writeChannel, "remote", 22, "127.0.0.1", 0) } returns streamForwarder + val blockingClient = BlockingSshClient(client) + + assertFalse(blockingClient.isAuthenticated) + assertSame(client.disconnectedFlow, blockingClient.disconnectedFlow) + assertSame(session, blockingClient.openSession()) + assertSame(sftp, blockingClient.openSftp()) + assertEquals(PingResult.NotSupported, blockingClient.ping()) + assertSame(localForwarder, blockingClient.localPortForward(bindAddress, "remote", 22)) + assertSame(localForwarder, blockingClient.localPortForward(8022, "remote", 22)) + assertSame(remoteForwarder, blockingClient.remotePortForward("0.0.0.0", 0, "localhost", 22)) + assertSame(dynamicForwarder, blockingClient.dynamicPortForward(bindAddress)) + assertSame(dynamicForwarder, blockingClient.dynamicPortForward(1080)) + assertSame(streamForwarder, blockingClient.forwardStream(readChannel, writeChannel, "remote", 22)) + + blockingClient.disconnect() + coVerify { client.disconnect() } + } + @Test fun `connect throws SshException when transport factory throws`() { val cause = TransportException("connection refused") diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/FakeSshServer.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/FakeSshServer.kt index a33726f..3c3a0b7 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/FakeSshServer.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/FakeSshServer.kt @@ -36,6 +36,13 @@ import org.connectbot.sshlib.crypto.SshPublicKeyEncoder import org.connectbot.sshlib.crypto.X25519ProviderFactory import org.connectbot.sshlib.crypto.encodeMpint import org.connectbot.sshlib.protocol.SshEnums +import org.connectbot.sshlib.protocol.SshMsgChannelFailure +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.SshMsgChannelSuccess +import org.connectbot.sshlib.protocol.SshMsgDisconnect import org.connectbot.sshlib.protocol.SshMsgExtInfo import org.connectbot.sshlib.protocol.SshMsgIgnore import org.connectbot.sshlib.protocol.SshMsgKexEcdhInit @@ -46,7 +53,10 @@ import org.connectbot.sshlib.protocol.SshMsgPong import org.connectbot.sshlib.protocol.SshMsgServiceAccept import org.connectbot.sshlib.protocol.SshMsgUserauthBanner import org.connectbot.sshlib.protocol.SshMsgUserauthFailure +import org.connectbot.sshlib.protocol.SshMsgUserauthInfoRequest +import org.connectbot.sshlib.protocol.SshMsgUserauthPkOk import org.connectbot.sshlib.protocol.SshMsgUserauthRequest +import org.connectbot.sshlib.protocol.SshMsgUserauthSuccess import org.connectbot.sshlib.protocol.createAsciiString import org.connectbot.sshlib.protocol.createByteString import org.connectbot.sshlib.protocol.createNameList @@ -94,6 +104,10 @@ class FakeSshServer( private val receivedExtInfo = Channel(Channel.UNLIMITED) private val receivedUserauthRequests = Channel(Channel.UNLIMITED) private val receivedClientKexInits = Channel(Channel.UNLIMITED) + private val receivedChannelOpens = Channel(Channel.UNLIMITED) + private val receivedChannelRequests = Channel(Channel.UNLIMITED) + private val receivedChannelOpenConfirmations = Channel(Channel.UNLIMITED) + private val receivedChannelOpenFailures = Channel(Channel.UNLIMITED) fun start() { scope.launch(coroutineContext) { serve() } @@ -204,6 +218,34 @@ class FakeSshServer( receivedUserauthRequests.trySend(request) } + SshEnums.MessageType.SSH_MSG_CHANNEL_OPEN -> { + val bodyBytes = rawBytes.copyOfRange(1, rawBytes.size) + val request = SshMsgChannelOpen(ByteBufferKaitaiStream(bodyBytes)) + request._read() + receivedChannelOpens.trySend(request) + } + + SshEnums.MessageType.SSH_MSG_CHANNEL_REQUEST -> { + val bodyBytes = rawBytes.copyOfRange(1, rawBytes.size) + val request = SshMsgChannelRequest(ByteBufferKaitaiStream(bodyBytes)) + request._read() + receivedChannelRequests.trySend(request) + } + + SshEnums.MessageType.SSH_MSG_CHANNEL_OPEN_CONFIRMATION -> { + val bodyBytes = rawBytes.copyOfRange(1, rawBytes.size) + val confirmation = SshMsgChannelOpenConfirmation(ByteBufferKaitaiStream(bodyBytes)) + confirmation._read() + receivedChannelOpenConfirmations.trySend(confirmation) + } + + SshEnums.MessageType.SSH_MSG_CHANNEL_OPEN_FAILURE -> { + val bodyBytes = rawBytes.copyOfRange(1, rawBytes.size) + val failure = SshMsgChannelOpenFailure(ByteBufferKaitaiStream(bodyBytes)) + failure._read() + receivedChannelOpenFailures.trySend(failure) + } + SshEnums.MessageType.SSH_MSG_PING -> { val bodyBytes = rawBytes.copyOfRange(1, rawBytes.size) val pingMsg = SshMsgPing(ByteBufferKaitaiStream(bodyBytes)) @@ -549,6 +591,15 @@ class FakeSshServer( } } + suspend fun sendServerPong(data: ByteArray) { + val pong = SshMsgPong() + pong.setData(createByteString(data)) + pong._check() + writeMutex.withLock { + serverIo.writePacket(SshEnums.MessageType.SSH_MSG_PONG.id().toInt(), pong.toByteArray()) + } + } + suspend fun sendUserauthBanner(message: String) { val banner = SshMsgUserauthBanner() val utf8 = createUtf8String(message) @@ -570,6 +621,217 @@ class FakeSshServer( } } + suspend fun sendUserauthSuccess() { + val success = SshMsgUserauthSuccess() + success._check() + writeMutex.withLock { + serverIo.writePacket(SshEnums.MessageType.SSH_MSG_USERAUTH_SUCCESS.id().toInt(), success.toByteArray()) + } + } + + suspend fun sendUserauthPkOk(algorithmName: String, publicKeyBlob: ByteArray) { + val pkOk = SshMsgUserauthPkOk() + pkOk.setPublicKeyAlgorithmName(createAsciiString(algorithmName)) + pkOk.setPublicKeyBlob(createByteString(publicKeyBlob)) + pkOk._check() + writeMutex.withLock { + serverIo.writePacket( + SshEnums.MessageType.SSH_MSG_USERAUTH_METHOD_SPECIFIC_60.id().toInt(), + pkOk.toByteArray(), + ) + } + } + + suspend fun sendUserauthInfoRequest( + name: String, + instruction: String, + prompts: List>, + ) { + val request = SshMsgUserauthInfoRequest() + request.setName(createByteString(name.toByteArray(Charsets.UTF_8))) + request.setInstruction(createByteString(instruction.toByteArray(Charsets.UTF_8))) + request.setLanguageTag(createByteString(ByteArray(0))) + request.setNumPrompts(prompts.size.toLong()) + val promptMessages = prompts.map { (prompt, echo) -> + SshMsgUserauthInfoRequest.Prompt().apply { + set_root(request) + set_parent(request) + setPrompt(createByteString(prompt.toByteArray(Charsets.UTF_8))) + setEcho(if (echo) 1 else 0) + _check() + } + } + request.setPrompts(ArrayList(promptMessages)) + request._check() + writeMutex.withLock { + serverIo.writePacket( + SshEnums.MessageType.SSH_MSG_USERAUTH_METHOD_SPECIFIC_60.id().toInt(), + request.toByteArray(), + ) + } + } + + suspend fun sendChannelOpenConfirmation( + recipientChannel: Int, + senderChannel: Int, + initialWindowSize: Int = 64 * 1024, + maximumPacketSize: Int = 32 * 1024, + ) { + val confirmation = SshMsgChannelOpenConfirmation() + confirmation.setRecipientChannel(recipientChannel.toLong()) + confirmation.setSenderChannel(senderChannel.toLong()) + confirmation.setInitialWindowSize(initialWindowSize.toLong()) + confirmation.setMaximumPacketSize(maximumPacketSize.toLong()) + confirmation._check() + writeMutex.withLock { + serverIo.writePacket( + SshEnums.MessageType.SSH_MSG_CHANNEL_OPEN_CONFIRMATION.id().toInt(), + confirmation.toByteArray(), + ) + } + } + + suspend fun sendChannelOpenFailure(recipientChannel: Int) { + val failure = SshMsgChannelOpenFailure() + failure.setRecipientChannel(recipientChannel.toLong()) + failure.setReasonCode(2) + failure.setDescription(createByteString("open failed".toByteArray(Charsets.UTF_8))) + failure.setLanguageTag(createByteString(ByteArray(0))) + failure._check() + writeMutex.withLock { + serverIo.writePacket(SshEnums.MessageType.SSH_MSG_CHANNEL_OPEN_FAILURE.id().toInt(), failure.toByteArray()) + } + } + + suspend fun sendChannelSuccess(recipientChannel: Int) { + val success = SshMsgChannelSuccess() + success.setRecipientChannel(recipientChannel.toLong()) + success._check() + writeMutex.withLock { + serverIo.writePacket(SshEnums.MessageType.SSH_MSG_CHANNEL_SUCCESS.id().toInt(), success.toByteArray()) + } + } + + suspend fun sendChannelFailure(recipientChannel: Int) { + val failure = SshMsgChannelFailure() + failure.setRecipientChannel(recipientChannel.toLong()) + failure._check() + writeMutex.withLock { + serverIo.writePacket(SshEnums.MessageType.SSH_MSG_CHANNEL_FAILURE.id().toInt(), failure.toByteArray()) + } + } + + suspend fun sendChannelData(recipientChannel: Int, data: ByteArray) { + val payload = ByteArrayOutputStream() + payload.write(ByteBuffer.allocate(4).putInt(recipientChannel).array()) + payload.writeString(data) + writeMutex.withLock { + serverIo.writePacket(SshEnums.MessageType.SSH_MSG_CHANNEL_DATA.id().toInt(), payload.toByteArray()) + } + } + + suspend fun sendChannelExtendedData(recipientChannel: Int, dataTypeCode: Int, data: ByteArray) { + val payload = ByteArrayOutputStream() + payload.write(ByteBuffer.allocate(4).putInt(recipientChannel).array()) + payload.write(ByteBuffer.allocate(4).putInt(dataTypeCode).array()) + payload.writeString(data) + writeMutex.withLock { + serverIo.writePacket(SshEnums.MessageType.SSH_MSG_CHANNEL_EXTENDED_DATA.id().toInt(), payload.toByteArray()) + } + } + + suspend fun sendChannelWindowAdjust(recipientChannel: Int, bytesToAdd: Long) { + val payload = ByteBuffer.allocate(8) + .putInt(recipientChannel) + .putInt(bytesToAdd.toInt()) + .array() + writeMutex.withLock { + serverIo.writePacket(SshEnums.MessageType.SSH_MSG_CHANNEL_WINDOW_ADJUST.id().toInt(), payload) + } + } + + suspend fun sendChannelEof(recipientChannel: Int) { + val payload = ByteBuffer.allocate(4).putInt(recipientChannel).array() + writeMutex.withLock { + serverIo.writePacket(SshEnums.MessageType.SSH_MSG_CHANNEL_EOF.id().toInt(), payload) + } + } + + suspend fun sendChannelClose(recipientChannel: Int) { + val payload = ByteBuffer.allocate(4).putInt(recipientChannel).array() + writeMutex.withLock { + serverIo.writePacket(SshEnums.MessageType.SSH_MSG_CHANNEL_CLOSE.id().toInt(), payload) + } + } + + suspend fun sendRequestSuccess(payload: ByteArray = ByteArray(0)) { + writeMutex.withLock { + serverIo.writePacket(SshEnums.MessageType.SSH_MSG_REQUEST_SUCCESS.id().toInt(), payload) + } + } + + suspend fun sendRequestFailure() { + writeMutex.withLock { + serverIo.writePacket(SshEnums.MessageType.SSH_MSG_REQUEST_FAILURE.id().toInt()) + } + } + + suspend fun sendDisconnect(description: String = "bye") { + val msg = SshMsgDisconnect() + msg.setReasonCode(SshEnums.DisconnectReason.SSH_DISCONNECT_BY_APPLICATION) + msg.setDescription(createUtf8String(description)) + msg.setLanguage(createAsciiString("")) + msg._check() + writeMutex.withLock { + serverIo.writePacket(SshEnums.MessageType.SSH_MSG_DISCONNECT.id().toInt(), msg.toByteArray()) + } + } + + suspend fun sendGlobalRequest(requestName: String, wantReply: Boolean) { + val payload = ByteArrayOutputStream() + payload.writeString(requestName.toByteArray(Charsets.US_ASCII)) + payload.write(if (wantReply) 1 else 0) + writeMutex.withLock { + serverIo.writePacket(SshEnums.MessageType.SSH_MSG_GLOBAL_REQUEST.id().toInt(), payload.toByteArray()) + } + } + + suspend fun sendChannelOpen(channelType: String, senderChannel: Int) { + sendChannelOpenPacket(channelType, senderChannel, channelSpecificData = ByteArray(0)) + } + + suspend fun sendForwardedTcpipChannelOpen( + senderChannel: Int, + connectedAddress: String, + connectedPort: Int, + originatorAddress: String, + originatorPort: Int, + ) { + val data = ByteArrayOutputStream() + data.writeString(connectedAddress.toByteArray(Charsets.US_ASCII)) + data.write(ByteBuffer.allocate(4).putInt(connectedPort).array()) + data.writeString(originatorAddress.toByteArray(Charsets.US_ASCII)) + data.write(ByteBuffer.allocate(4).putInt(originatorPort).array()) + sendChannelOpenPacket("forwarded-tcpip", senderChannel, data.toByteArray()) + } + + private suspend fun sendChannelOpenPacket(channelType: String, senderChannel: Int, channelSpecificData: ByteArray) { + val payload = ByteArrayOutputStream() + payload.writeString(channelType.toByteArray(Charsets.US_ASCII)) + payload.write(ByteBuffer.allocate(4).putInt(senderChannel).array()) + payload.write(ByteBuffer.allocate(4).putInt(64 * 1024).array()) + payload.write(ByteBuffer.allocate(4).putInt(32 * 1024).array()) + payload.write(channelSpecificData) + writeMutex.withLock { + serverIo.writePacket(SshEnums.MessageType.SSH_MSG_CHANNEL_OPEN.id().toInt(), payload.toByteArray()) + } + } + + private fun ByteArrayOutputStream.writeString(data: ByteArray) { + write(ByteBuffer.allocate(4).putInt(data.size).array()) + write(data) + } + suspend fun awaitPong(): ByteArray = receivedPongs.receive() suspend fun awaitExtInfo(): SshMsgExtInfo = receivedExtInfo.receive() @@ -577,4 +839,12 @@ class FakeSshServer( suspend fun awaitUserauthRequest(): SshMsgUserauthRequest = receivedUserauthRequests.receive() suspend fun awaitClientKexInit(): SshMsgKexinit = receivedClientKexInits.receive() + + suspend fun awaitChannelOpen(): SshMsgChannelOpen = receivedChannelOpens.receive() + + suspend fun awaitChannelRequest(): SshMsgChannelRequest = receivedChannelRequests.receive() + + suspend fun awaitChannelOpenConfirmation(): SshMsgChannelOpenConfirmation = receivedChannelOpenConfirmations.receive() + + suspend fun awaitChannelOpenFailure(): SshMsgChannelOpenFailure = receivedChannelOpenFailures.receive() } 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 a06f6bb..8db3045 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/Socks5HandlerTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/Socks5HandlerTest.kt @@ -1,6 +1,6 @@ /* * ConnectBot SSH Library - * Copyright 2025 Kenny Root + * Copyright 2025-2026 Kenny Root * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -214,6 +214,72 @@ class Socks5HandlerTest { assertNull(result) } + @Test + fun `zero auth methods returns null`() = runTest { + val handler = Socks5Handler() + val readChannel = ByteReadChannel(byteArrayOf(0x05, 0x00)) + val writeChannel = ByteChannel(true) + + assertNull(handler.handleHandshake(readChannel, writeChannel)) + } + + @Test + fun `bad username password auth version returns null`() = runTest { + val authenticator = object : Socks5Authenticator { + override fun authenticate(username: String, password: String): Boolean = true + } + val handler = Socks5Handler(authenticator) + val input = byteArrayOf( + 0x05, + 0x01, + 0x02, + 0x02, // invalid RFC 1929 auth version + ) + val readChannel = ByteReadChannel(input) + val writeChannel = ByteChannel(true) + + assertNull(handler.handleHandshake(readChannel, writeChannel)) + } + + @Test + fun `bad connect request version returns null`() = runTest { + val handler = Socks5Handler() + val input = byteArrayOf( + 0x05, 0x01, 0x00, + 0x04, 0x01, 0x00, 0x03, 0x00, 0x00, 0x50, + ) + val readChannel = ByteReadChannel(input) + val writeChannel = ByteChannel(true) + + assertNull(handler.handleHandshake(readChannel, writeChannel)) + } + + @Test + fun `unsupported command returns null`() = runTest { + val handler = Socks5Handler() + val input = byteArrayOf( + 0x05, 0x01, 0x00, + 0x05, 0x02, 0x00, 0x03, 0x00, 0x00, 0x50, + ) + val readChannel = ByteReadChannel(input) + val writeChannel = ByteChannel(true) + + assertNull(handler.handleHandshake(readChannel, writeChannel)) + } + + @Test + fun `unsupported address type returns null`() = runTest { + val handler = Socks5Handler() + val input = byteArrayOf( + 0x05, 0x01, 0x00, + 0x05, 0x01, 0x00, 0x7f, 0x00, 0x50, + ) + val readChannel = ByteReadChannel(input) + val writeChannel = ByteChannel(true) + + assertNull(handler.handleHandshake(readChannel, writeChannel)) + } + @Test fun `username password auth succeeds`() = runTest { val authenticator = object : Socks5Authenticator { diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/SshConnectionFlowTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/SshConnectionFlowTest.kt new file mode 100644 index 0000000..05c57e5 --- /dev/null +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/SshConnectionFlowTest.kt @@ -0,0 +1,495 @@ +/* + * ConnectBot SSH Library + * Copyright 2026 Kenny Root + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.sshlib.client + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withTimeout +import kotlinx.coroutines.yield +import org.connectbot.sshlib.AgentIdentity +import org.connectbot.sshlib.AgentProvider +import org.connectbot.sshlib.AgentSigningContext +import org.connectbot.sshlib.AuthHandler +import org.connectbot.sshlib.AuthPublicKey +import org.connectbot.sshlib.AuthResult +import org.connectbot.sshlib.ConnectResult +import org.connectbot.sshlib.HostKeyVerifier +import org.connectbot.sshlib.KeyboardInteractiveCallback +import org.connectbot.sshlib.PublicKey +import org.connectbot.sshlib.crypto.PrivateKeyReader +import org.connectbot.sshlib.crypto.SshPublicKeyEncoder +import org.connectbot.sshlib.transport.PipedTransport +import org.junit.jupiter.api.Test +import java.nio.ByteBuffer +import java.nio.file.Files +import java.nio.file.Paths +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +@OptIn(ExperimentalCoroutinesApi::class) +class SshConnectionFlowTest { + + private val acceptAllVerifier = object : HostKeyVerifier { + override suspend fun verify(key: PublicKey): Boolean = true + } + + @Test + fun `password authentication handles success and failure replies`() = runTest { + connectedFixture { connection, server, dispatcher -> + val success = async(dispatcher) { connection.authenticatePassword("user", "pass") } + val successRequest = withTimeout(5_000) { server.awaitUserauthRequest() } + assertEquals("password", successRequest.methodName().value()) + server.sendUserauthSuccess() + assertEquals(AuthResult.Success, withTimeout(5_000) { success.await() }) + } + + connectedFixture { connection, server, dispatcher -> + val failure = async(dispatcher) { connection.authenticatePassword("user", "bad") } + val failureRequest = withTimeout(5_000) { server.awaitUserauthRequest() } + assertEquals("password", failureRequest.methodName().value()) + server.sendUserauthFailure(setOf("publickey", "keyboard-interactive"), partialSuccess = false) + + assertIs(withTimeout(5_000) { failure.await() }) + } + } + + @Test + fun `public key authentication signs and handles success reply`() = runTest { + connectedFixture { connection, server, dispatcher -> + val privateKeyData = Files.readString(Paths.get("src/test/resources/keys/ed25519_unencrypted")) + val privateKey = PrivateKeyReader.read(privateKeyData) + + val auth = async(dispatcher) { connection.authenticatePublicKey("user", privateKey) } + val request = withTimeout(5_000) { server.awaitUserauthRequest() } + assertEquals("publickey", request.methodName().value()) + server.sendUserauthSuccess() + + assertEquals(AuthResult.Success, withTimeout(5_000) { auth.await() }) + } + } + + @Test + fun `direct keyboard interactive authentication handles info request`() = runTest { + connectedFixture { connection, server, dispatcher -> + val callback = object : KeyboardInteractiveCallback { + override suspend fun onInfoRequest( + name: String, + instruction: String, + prompts: List, + respond: suspend (responses: List) -> Unit, + ) { + assertEquals("login", name) + assertEquals("challenge", prompts.single().text) + respond(listOf("answer")) + } + } + + val auth = async(dispatcher) { connection.authenticateKeyboardInteractive("user", callback) } + val request = withTimeout(5_000) { server.awaitUserauthRequest() } + assertEquals("keyboard-interactive", request.methodName().value()) + + server.sendUserauthInfoRequest("login", "instruction", listOf("challenge" to false)) + yield() + server.sendUserauthSuccess() + + assertEquals(AuthResult.Success, withTimeout(5_000) { auth.await() }) + } + } + + @Test + fun `strategy authentication discovers methods and succeeds with password`() = runTest { + connectedFixture { connection, server, dispatcher -> + val handler = object : EmptyAuthHandler() { + override suspend fun onPasswordNeeded(): String = "secret" + } + + val auth = async(dispatcher) { connection.authenticate("user", handler) } + val none = withTimeout(5_000) { server.awaitUserauthRequest() } + assertEquals("none", none.methodName().value()) + server.sendUserauthFailure(setOf("password"), partialSuccess = false) + + val password = withTimeout(5_000) { server.awaitUserauthRequest() } + assertEquals("password", password.methodName().value()) + server.sendUserauthSuccess() + + assertEquals(AuthResult.Success, withTimeout(5_000) { auth.await() }) + } + } + + @Test + fun `strategy authentication handles keyboard interactive prompt`() = runTest { + connectedFixture { connection, server, dispatcher -> + val handler = object : EmptyAuthHandler() { + override suspend fun onKeyboardInteractivePrompt( + name: String, + instruction: String, + prompts: List, + ): List { + assertEquals("login", name) + assertEquals("answer", prompts.single().text) + return listOf("response") + } + } + + val auth = async(dispatcher) { connection.authenticate("user", handler) } + assertEquals("none", withTimeout(5_000) { server.awaitUserauthRequest() }.methodName().value()) + server.sendUserauthFailure(setOf("keyboard-interactive"), partialSuccess = false) + + assertEquals("keyboard-interactive", withTimeout(5_000) { server.awaitUserauthRequest() }.methodName().value()) + server.sendUserauthInfoRequest("login", "instruction", listOf("answer" to false)) + yield() + server.sendUserauthSuccess() + + assertEquals(AuthResult.Success, withTimeout(5_000) { auth.await() }) + } + } + + @Test + fun `strategy authentication probes and signs accepted public key`() = runTest { + connectedFixture { connection, server, dispatcher -> + val privateKeyData = Files.readString(Paths.get("src/test/resources/keys/ed25519_unencrypted")) + val privateKey = PrivateKeyReader.read(privateKeyData) + val publicKeyBlob = SshPublicKeyEncoder.encode(privateKey.jcaKeyPair, privateKey.keyType) + val authKey = AuthPublicKey(privateKey.signatureAlgorithm, publicKeyBlob) + val handler = object : EmptyAuthHandler() { + override suspend fun onPublicKeysNeeded(): List = listOf(authKey) + override suspend fun onSignatureRequest(key: AuthPublicKey, dataToSign: ByteArray): ByteArray = byteArrayOf(1, 2, 3) + } + + val auth = async(dispatcher) { connection.authenticate("user", handler) } + assertEquals("none", withTimeout(5_000) { server.awaitUserauthRequest() }.methodName().value()) + server.sendUserauthFailure(setOf("publickey"), partialSuccess = false) + + val probe = withTimeout(5_000) { server.awaitUserauthRequest() } + assertEquals("publickey", probe.methodName().value()) + server.sendUserauthPkOk(authKey.algorithmName, authKey.publicKeyBlob) + + val signed = withTimeout(5_000) { server.awaitUserauthRequest() } + assertEquals("publickey", signed.methodName().value()) + server.sendUserauthSuccess() + + assertEquals(AuthResult.Success, withTimeout(5_000) { auth.await() }) + } + } + + @Test + fun `session channel open handles confirmation request success and failure`() = runTest { + connectedFixture { connection, server, dispatcher -> + authenticate(connection, server, dispatcher) + + val open = async(dispatcher) { connection.openSessionChannel() } + val openRequest = withTimeout(5_000) { server.awaitChannelOpen() } + assertEquals("session", openRequest.channelType().value()) + server.sendChannelOpenConfirmation(openRequest.senderChannel().toInt(), senderChannel = 100) + + val session = assertNotNull(withTimeout(5_000) { open.await() }) + + val exec = async(dispatcher) { session.requestExec("true") } + val request = withTimeout(5_000) { server.awaitChannelRequest() } + assertEquals("exec", request.requestType().value()) + server.sendChannelSuccess(request.recipientChannel().toInt()) + assertTrue(withTimeout(5_000) { exec.await() }) + + val shell = async(dispatcher) { session.requestShell() } + val shellRequest = withTimeout(5_000) { server.awaitChannelRequest() } + assertEquals("shell", shellRequest.requestType().value()) + server.sendChannelFailure(shellRequest.recipientChannel().toInt()) + assertEquals(false, withTimeout(5_000) { shell.await() }) + } + } + + @Test + fun `session channel open returns null on open failure`() = runTest { + connectedFixture { connection, server, dispatcher -> + authenticate(connection, server, dispatcher) + + val open = async(dispatcher) { connection.openSessionChannel() } + val openRequest = withTimeout(5_000) { server.awaitChannelOpen() } + server.sendChannelOpenFailure(openRequest.senderChannel().toInt()) + + assertNull(withTimeout(5_000) { open.await() }) + } + } + + @Test + fun `session channel routes data extended data eof and close`() = runTest { + connectedFixture { connection, server, dispatcher -> + authenticate(connection, server, dispatcher) + val session = openSession(connection, server, dispatcher) + val localChannel = session.localChannelNumber + + server.sendChannelData(localChannel, byteArrayOf(1, 2, 3)) + assertContentEquals(byteArrayOf(1, 2, 3), withTimeout(5_000) { session.stdout.receive() }) + + server.sendChannelExtendedData(localChannel, 1, byteArrayOf(4, 5)) + val stderr = withTimeout(5_000) { session.stderr.receive() } + assertContentEquals(byteArrayOf(4, 5), stderr) + + server.sendChannelWindowAdjust(localChannel, 1024) + server.sendChannelEof(localChannel) + assertNull(withTimeout(5_000) { session.read() }) + + server.sendChannelClose(localChannel) + withTimeout(5_000) { + while (session.isOpen) { + yield() + } + } + } + } + + @Test + fun `direct tcpip channel routes data eof and close`() = runTest { + connectedFixture { connection, server, dispatcher -> + authenticate(connection, server, dispatcher) + + val open = async(dispatcher) { + connection.openDirectTcpipChannel("target", 22, "127.0.0.1", 12345) + } + val openRequest = withTimeout(5_000) { server.awaitChannelOpen() } + assertEquals("direct-tcpip", openRequest.channelType().value()) + server.sendChannelOpenConfirmation(openRequest.senderChannel().toInt(), senderChannel = 200) + val channel = assertNotNull(withTimeout(5_000) { open.await() }) + + server.sendChannelData(channel.localChannelNumber, byteArrayOf(9, 8)) + assertContentEquals(byteArrayOf(9, 8), withTimeout(5_000) { channel.incomingData.receive() }) + + server.sendChannelWindowAdjust(channel.localChannelNumber, 1024) + server.sendChannelEof(channel.localChannelNumber) + assertNull(withTimeout(5_000) { channel.incomingData.receiveCatching().getOrNull() }) + + server.sendChannelClose(channel.localChannelNumber) + withTimeout(5_000) { + while (channel.isOpen) { + yield() + } + } + } + } + + @Test + fun `incoming agent channel is rejected without provider and accepted with provider`() = runTest { + connectedFixture { connection, server, dispatcher -> + authenticate(connection, server, dispatcher) + + server.sendChannelOpen("unknown-channel-type", senderChannel = 76) + val unknownFailure = withTimeout(5_000) { server.awaitChannelOpenFailure() } + assertEquals(76, unknownFailure.recipientChannel().toInt()) + + server.sendChannelOpen("auth-agent@openssh.com", senderChannel = 77) + val failure = withTimeout(5_000) { server.awaitChannelOpenFailure() } + assertEquals(77, failure.recipientChannel().toInt()) + + connection.enableAgentForwarding( + object : AgentProvider { + override suspend fun getIdentities(): List = emptyList() + override suspend fun signData(context: AgentSigningContext): ByteArray? = null + }, + ) + + server.sendChannelOpen("auth-agent@openssh.com", senderChannel = 78) + val confirmation = withTimeout(5_000) { server.awaitChannelOpenConfirmation() } + assertEquals(78, confirmation.recipientChannel().toInt()) + + val agentLocalChannel = confirmation.senderChannel().toInt() + server.sendChannelData(agentLocalChannel, ByteBuffer.allocate(5).putInt(1).put(11).array()) + server.sendChannelWindowAdjust(agentLocalChannel, 1024) + server.sendChannelEof(agentLocalChannel) + server.sendChannelClose(agentLocalChannel) + yield() + } + } + + @Test + fun `tcpip forward request handles assigned port success fixed port success and failure`() = runTest { + connectedFixture { connection, server, dispatcher -> + authenticate(connection, server, dispatcher) + + val assigned = async(dispatcher) { connection.sendTcpipForwardRequest("127.0.0.1", 0) } + yield() + server.sendRequestSuccess(ByteBuffer.allocate(4).putInt(9022).array()) + assertEquals(9022, withTimeout(5_000) { assigned.await() }) + + val fixed = async(dispatcher) { connection.sendTcpipForwardRequest("127.0.0.1", 2022) } + yield() + server.sendRequestSuccess() + assertEquals(2022, withTimeout(5_000) { fixed.await() }) + + val rejected = async(dispatcher) { connection.sendTcpipForwardRequest("127.0.0.1", 3022) } + yield() + server.sendRequestFailure() + assertNull(withTimeout(5_000) { rejected.await() }) + + connection.sendCancelTcpipForward("127.0.0.1", 2022) + } + } + + @Test + fun `server disconnect is emitted on disconnected flow`() = runTest { + connectedFixture { connection, server, dispatcher -> + authenticate(connection, server, dispatcher) + + server.sendDisconnect("finished") + + withTimeout(5_000) { connection.disconnectedFlow.first() } + } + } + + @Test + fun `unhandled global request with want reply is rejected`() = runTest { + connectedFixture { connection, server, dispatcher -> + authenticate(connection, server, dispatcher) + + server.sendGlobalRequest("keepalive@example.com", wantReply = true) + yield() + } + } + + @Test + fun `unexpected pong payloads are ignored`() = runTest { + connectedFixture { connection, server, dispatcher -> + authenticate(connection, server, dispatcher) + + server.sendServerPong(byteArrayOf(1, 2, 3)) + server.sendServerPong(ByteBuffer.allocate(8).putLong(999).array()) + yield() + } + } + + @Test + fun `incoming forwarded tcpip channel rejects missing handler and invokes registered handler`() = runTest { + connectedFixture { connection, server, dispatcher -> + authenticate(connection, server, dispatcher) + + server.sendForwardedTcpipChannelOpen( + senderChannel = 88, + connectedAddress = "127.0.0.1", + connectedPort = 8022, + originatorAddress = "10.0.0.1", + originatorPort = 50000, + ) + val failure = withTimeout(5_000) { server.awaitChannelOpenFailure() } + assertEquals(88, failure.recipientChannel().toInt()) + + val forwarded = CompletableDeferred>() + connection.registerRemoteForwarder("127.0.0.1:8022") { connectedAddr, connectedPort, originAddr, originPort, senderChannel, initialWindow, maxPacketSize -> + forwarded.complete(listOf(connectedAddr, connectedPort, originAddr, originPort, senderChannel, initialWindow, maxPacketSize)) + } + + server.sendForwardedTcpipChannelOpen( + senderChannel = 89, + connectedAddress = "127.0.0.1", + connectedPort = 8022, + originatorAddress = "10.0.0.2", + originatorPort = 50001, + ) + + assertEquals( + listOf("127.0.0.1", 8022, "10.0.0.2", 50001, 89, 65536L, 32768), + withTimeout(5_000) { forwarded.await() }, + ) + } + } + + private suspend fun authenticate( + connection: SshConnection, + server: FakeSshServer, + dispatcher: CoroutineDispatcher, + ) { + val auth = CoroutineScope(dispatcher).async { connection.authenticatePassword("user", "pass") } + withTimeout(5_000) { server.awaitUserauthRequest() } + server.sendUserauthSuccess() + assertEquals(AuthResult.Success, withTimeout(5_000) { auth.await() }) + } + + private suspend fun openSession( + connection: SshConnection, + server: FakeSshServer, + dispatcher: CoroutineDispatcher, + ): SessionChannel { + val open = CoroutineScope(dispatcher).async { connection.openSessionChannel() } + val openRequest = withTimeout(5_000) { server.awaitChannelOpen() } + assertEquals("session", openRequest.channelType().value()) + server.sendChannelOpenConfirmation(openRequest.senderChannel().toInt(), senderChannel = 100) + return assertNotNull(withTimeout(5_000) { open.await() }) + } + + private open class EmptyAuthHandler : AuthHandler { + override suspend fun onPublicKeysNeeded(): List = emptyList() + override suspend fun onSignatureRequest(key: AuthPublicKey, dataToSign: ByteArray): ByteArray? = null + override suspend fun onKeyboardInteractivePrompt( + name: String, + instruction: String, + prompts: List, + ): List? = null + override suspend fun onPasswordNeeded(): String? = null + } + + private suspend fun TestScope.connectedFixture( + block: suspend (SshConnection, FakeSshServer, CoroutineDispatcher) -> Unit, + ) { + val dispatcher = StandardTestDispatcher(testScheduler) + val (clientTransport, serverTransport) = PipedTransport.create() + val server = FakeSshServer(serverTransport, backgroundScope, dispatcher) + server.start() + + val connection = SshConnection( + transport = clientTransport, + hostKeyVerifier = acceptAllVerifier, + rekeyIntervalMs = Long.MAX_VALUE, + rekeyBytesLimit = Long.MAX_VALUE, + coroutineDispatcher = dispatcher, + ) + + try { + val connectResult = connectInBackground(connection, backgroundScope, dispatcher) + assertIs(connectResult) + val info = assertNotNull(connection.connectionInfo) + assertEquals("curve25519-sha256", info.kexAlgorithm) + assertEquals("ssh-ed25519", info.serverHostKeyAlgorithm) + assertEquals("aes128-ctr", info.encryptionAlgorithmC2S) + assertEquals("aes128-ctr", info.encryptionAlgorithmS2C) + block(connection, server, dispatcher) + } finally { + connection.close() + } + } + + private suspend fun connectInBackground( + connection: SshConnection, + scope: CoroutineScope, + dispatcher: CoroutineDispatcher, + ): ConnectResult { + val result = CompletableDeferred() + scope.launch(dispatcher) { result.complete(connection.connect()) } + yield() + return result.await() + } +} diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/sftp/SftpClientImplTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/sftp/SftpClientImplTest.kt new file mode 100644 index 0000000..0662ec7 --- /dev/null +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/sftp/SftpClientImplTest.kt @@ -0,0 +1,449 @@ +/* + * ConnectBot SSH Library + * Copyright 2025-2026 Kenny Root + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.sshlib.client.sftp + +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.runBlocking +import org.connectbot.sshlib.SftpAttributes +import org.connectbot.sshlib.SftpClient +import org.connectbot.sshlib.SftpDirectoryEntry +import org.connectbot.sshlib.SftpFileHandle +import org.connectbot.sshlib.SftpOpenFlag +import org.connectbot.sshlib.SftpResult +import org.connectbot.sshlib.SftpStatusCode +import org.connectbot.sshlib.SshSession +import org.junit.jupiter.api.Test +import java.nio.ByteBuffer +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertIs +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class SftpClientImplTest { + + @Test + fun `create negotiates version and close is idempotent`() = runBlocking { + val session = FakeSshSession() + session.enqueueRead(packet(SSH_FXP_VERSION, ByteBuffer.allocate(4).putInt(5).array())) + val client = createClient(session) + + assertEquals(3, client.protocolVersion) + assertTrue(client.isOpen) + client.close() + client.close() + assertFalse(client.isOpen) + assertEquals(1, session.closeCalls) + } + + @Test + fun `create returns protocol errors for malformed version response`() = runBlocking { + val wrongTypeSession = FakeSshSession() + wrongTypeSession.enqueueRead(packet(SSH_FXP_STATUS, statusPayload(SftpStatusCode.OK))) + assertIs(SftpClientImpl.create(wrongTypeSession)) + + val shortPayloadSession = FakeSshSession() + shortPayloadSession.enqueueRead(packet(SSH_FXP_VERSION, byteArrayOf(0, 0, 0))) + assertIs(SftpClientImpl.create(shortPayloadSession)) + } + + @Test + fun `create propagates write and read errors`() = runBlocking { + val writeFailureSession = FakeSshSession(writeFailure = IllegalStateException("cannot write")) + assertIs(SftpClientImpl.create(writeFailureSession)) + + val readFailureSession = FakeSshSession() + assertIs(SftpClientImpl.create(readFailureSession)) + } + + @Test + fun `create propagates protocol and server handshake results`() = runBlocking { + assertIs( + SftpClientImpl.create( + FakeSshSession(), + FakeHandshakeTransport(writeResult = SftpResult.ProtocolError("init rejected")), + ), + ) + assertIs( + SftpClientImpl.create( + FakeSshSession(), + FakeHandshakeTransport(writeResult = SftpResult.ServerError(SftpStatusCode.FAILURE, "init failed")), + ), + ) + assertIs( + SftpClientImpl.create( + FakeSshSession(), + FakeHandshakeTransport(readResult = SftpResult.ProtocolError("bad version")), + ), + ) + assertIs( + SftpClientImpl.create( + FakeSshSession(), + FakeHandshakeTransport(readResult = SftpResult.ServerError(SftpStatusCode.FAILURE, "version failed")), + ), + ) + } + + @Test + fun `file operations map successful responses`() = runBlocking { + val session = FakeSshSession( + responseFor = { type, _ -> + when (type) { + SSH_FXP_OPEN -> response(SSH_FXP_HANDLE, stringPayload(byteArrayOf(1, 2, 3))) + SSH_FXP_READ -> response(SSH_FXP_DATA, stringPayload(byteArrayOf(4, 5, 6))) + SSH_FXP_WRITE, SSH_FXP_CLOSE -> response(SSH_FXP_STATUS, statusPayload(SftpStatusCode.OK)) + else -> error("unexpected request $type") + } + }, + ) + val client = createClient(session) + + val handle = assertSuccess(client.open("/tmp/file", setOf(SftpOpenFlag.READ, SftpOpenFlag.WRITE))) + assertContentEquals(byteArrayOf(1, 2, 3), handle.handle) + assertContentEquals(byteArrayOf(4, 5, 6), assertSuccess(client.read(handle, 7, 3))!!) + assertEquals(SftpResult.Success(Unit), client.write(handle, 8, byteArrayOf(9))) + assertEquals(SftpResult.Success(Unit), client.close(handle)) + + assertEquals(listOf(SSH_FXP_OPEN, SSH_FXP_READ, SSH_FXP_WRITE, SSH_FXP_CLOSE), session.requestTypes) + } + + @Test + fun `file operations map eof status errors and unexpected packets`() = runBlocking { + val handle = SftpFileHandle(byteArrayOf(1)) + val eofClient = createClient( + FakeSshSession(responseFor = { type, _ -> + when (type) { + SSH_FXP_READ -> response(SSH_FXP_STATUS, statusPayload(SftpStatusCode.EOF)) + SSH_FXP_CLOSE -> response(SSH_FXP_STATUS, statusPayload(SftpStatusCode.PERMISSION_DENIED, "denied")) + else -> response(SSH_FXP_STATUS, statusPayload(SftpStatusCode.NO_SUCH_FILE, "missing")) + } + }), + ) + + val readErrorClient = createClient( + FakeSshSession(responseFor = { _, _ -> response(SSH_FXP_STATUS, statusPayload(SftpStatusCode.FAILURE, "failed")) }), + ) + assertIs(readErrorClient.read(handle, 0, 1)) + + assertEquals(SftpResult.Success(null), eofClient.read(handle, 0, 1)) + val closeError = assertIs(eofClient.close(handle)) + assertEquals(SftpStatusCode.PERMISSION_DENIED, closeError.statusCode) + assertEquals("denied", closeError.message) + assertIs(eofClient.open("/missing", setOf(SftpOpenFlag.READ))) + + val protocolClient = createClient( + FakeSshSession(responseFor = { _, _ -> response(SSH_FXP_VERSION, byteArrayOf()) }), + ) + assertIs(protocolClient.open("/bad", setOf(SftpOpenFlag.READ))) + assertIs(protocolClient.read(handle, 0, 1)) + assertEquals(SftpResult.Success(Unit), protocolClient.close(handle)) + } + + @Test + fun `stat and mutation operations map responses`() = runBlocking { + val attrs = SftpAttributes(size = 42, permissions = 0b110_100_100) + val session = FakeSshSession( + responseFor = { type, _ -> + when (type) { + SSH_FXP_STAT, SSH_FXP_LSTAT, SSH_FXP_FSTAT -> response(SSH_FXP_ATTRS, SftpFileAttributes.encode(attrs)) + + SSH_FXP_SETSTAT, SSH_FXP_FSETSTAT, SSH_FXP_MKDIR, SSH_FXP_RMDIR, + SSH_FXP_REMOVE, SSH_FXP_RENAME, SSH_FXP_SYMLINK, + -> response(SSH_FXP_STATUS, statusPayload(SftpStatusCode.OK)) + + else -> error("unexpected request $type") + } + }, + ) + val client = createClient(session) + val handle = SftpFileHandle(byteArrayOf(1)) + + assertEquals(attrs, assertSuccess(client.stat("/path"))) + assertEquals(attrs, assertSuccess(client.lstat("/path"))) + assertEquals(attrs, assertSuccess(client.fstat(handle))) + assertEquals(SftpResult.Success(Unit), client.setstat("/path", attrs)) + assertEquals(SftpResult.Success(Unit), client.fsetstat(handle, attrs)) + assertEquals(SftpResult.Success(Unit), client.mkdir("/dir", attrs)) + assertEquals(SftpResult.Success(Unit), client.rmdir("/dir")) + assertEquals(SftpResult.Success(Unit), client.remove("/file")) + assertEquals(SftpResult.Success(Unit), client.rename("/old", "/new")) + assertEquals(SftpResult.Success(Unit), client.symlink("/target", "/link")) + } + + @Test + fun `stat and status operations map errors and unexpected success packets`() = runBlocking { + val errorClient = createClient( + FakeSshSession(responseFor = { _, _ -> response(SSH_FXP_STATUS, statusPayload(SftpStatusCode.FAILURE)) }), + ) + val handle = SftpFileHandle(byteArrayOf(1)) + + assertIs(errorClient.stat("/path")) + assertIs(errorClient.fstat(handle)) + assertIs(errorClient.remove("/path")) + + val protocolClient = createClient(FakeSshSession(responseFor = { _, _ -> response(SSH_FXP_VERSION, byteArrayOf()) })) + assertIs(protocolClient.stat("/path")) + assertIs(protocolClient.fstat(handle)) + assertEquals(SftpResult.Success(Unit), protocolClient.remove("/path")) + } + + @Test + fun `directory and path operations map name responses`() = runBlocking { + val entries = listOf( + SftpDirectoryEntry("alpha", "long alpha", SftpAttributes(size = 1)), + SftpDirectoryEntry("beta", "long beta", SftpAttributes(size = 2)), + ) + val session = FakeSshSession( + responseFor = { type, _ -> + when (type) { + SSH_FXP_OPENDIR -> response(SSH_FXP_HANDLE, stringPayload(byteArrayOf(9))) + SSH_FXP_READDIR -> response(SSH_FXP_NAME, namePayload(entries)) + SSH_FXP_REALPATH -> response(SSH_FXP_NAME, namePayload(listOf(entries[0]))) + SSH_FXP_READLINK -> response(SSH_FXP_NAME, namePayload(listOf(entries[1]))) + else -> error("unexpected request $type") + } + }, + ) + val client = createClient(session) + val handle = assertSuccess(client.opendir("/dir")) + + assertContentEquals(byteArrayOf(9), handle.handle) + assertEquals(entries, assertSuccess(client.readdir(handle))) + assertEquals("alpha", assertSuccess(client.realpath("."))) + assertEquals("beta", assertSuccess(client.readlink("/link"))) + } + + @Test + fun `directory and path operations map eof empty names and protocol errors`() = runBlocking { + val handle = SftpFileHandle(byteArrayOf(1)) + val eofClient = createClient( + FakeSshSession(responseFor = { _, _ -> response(SSH_FXP_STATUS, statusPayload(SftpStatusCode.EOF)) }), + ) + assertEquals(SftpResult.Success(null), eofClient.readdir(handle)) + + val errorClient = createClient( + FakeSshSession(responseFor = { _, _ -> response(SSH_FXP_STATUS, statusPayload(SftpStatusCode.FAILURE, "failed")) }), + ) + assertIs(errorClient.opendir("/dir")) + assertIs(errorClient.readdir(handle)) + assertIs(errorClient.realpath(".")) + assertIs(errorClient.readlink("/link")) + + val emptyNameClient = createClient( + FakeSshSession(responseFor = { _, _ -> response(SSH_FXP_NAME, namePayload(emptyList())) }), + ) + assertIs(emptyNameClient.realpath(".")) + assertIs(emptyNameClient.readlink("/link")) + + val protocolClient = createClient(FakeSshSession(responseFor = { _, _ -> response(SSH_FXP_VERSION, byteArrayOf()) })) + assertIs(protocolClient.opendir("/dir")) + assertIs(protocolClient.readdir(handle)) + assertIs(protocolClient.realpath(".")) + assertIs(protocolClient.readlink("/link")) + } + + @Test + fun `dispatcher propagates request write failures`() = runBlocking { + val client = createClient(FakeSshSession(failAfterHandshake = IllegalStateException("write failed"))) + + val result = client.remove("/file") + + assertIs(result) + } + + private suspend fun createClient(session: FakeSshSession): SftpClient { + session.enqueueRead(packet(SSH_FXP_VERSION, ByteBuffer.allocate(4).putInt(3).array())) + return assertSuccess(SftpClientImpl.create(session)) + } + + private fun response(type: Int, payload: ByteArray): SftpRawPacket = SftpRawPacket(type, payload) + + private fun packet(type: Int, payload: ByteArray): ByteArray { + val packet = ByteBuffer.allocate(4 + 1 + payload.size) + packet.putInt(1 + payload.size) + packet.put(type.toByte()) + packet.put(payload) + return packet.array() + } + + private fun responsePacket(type: Int, requestId: Int, payload: ByteArray): ByteArray { + val responsePayload = ByteBuffer.allocate(4 + payload.size) + responsePayload.putInt(requestId) + responsePayload.put(payload) + return packet(type, responsePayload.array()) + } + + private fun stringPayload(data: ByteArray): ByteArray { + val payload = ByteBuffer.allocate(4 + data.size) + payload.putInt(data.size) + payload.put(data) + return payload.array() + } + + private fun statusPayload(code: SftpStatusCode, message: String = code.name): ByteArray { + val msg = message.toByteArray(Charsets.UTF_8) + val lang = ByteArray(0) + val payload = ByteBuffer.allocate(4 + 4 + msg.size + 4 + lang.size) + payload.putInt(code.code) + payload.putInt(msg.size) + payload.put(msg) + payload.putInt(lang.size) + payload.put(lang) + return payload.array() + } + + private fun namePayload(entries: List): ByteArray { + val encodedEntries = entries.map { entry -> + val filename = entry.filename.toByteArray(Charsets.UTF_8) + val longname = entry.longname.toByteArray(Charsets.UTF_8) + val attrs = SftpFileAttributes.encode(entry.attrs) + ByteBuffer.allocate(4 + filename.size + 4 + longname.size + attrs.size).apply { + putInt(filename.size) + put(filename) + putInt(longname.size) + put(longname) + put(attrs) + }.array() + } + val payload = ByteBuffer.allocate(4 + encodedEntries.sumOf { it.size }) + payload.putInt(entries.size) + encodedEntries.forEach(payload::put) + return payload.array() + } + + private fun assertSuccess(result: SftpResult): T { + assertIs>(result) + return result.value + } + + private inner class FakeSshSession( + private val responseFor: (type: Int, payload: ByteArray) -> SftpRawPacket = { _, _ -> + response(SSH_FXP_STATUS, statusPayload(SftpStatusCode.OK)) + }, + private val writeFailure: Throwable? = null, + private val failAfterHandshake: Throwable? = null, + ) : SshSession { + private val reads = Channel(Channel.UNLIMITED) + private var writes = 0 + private var open = true + val requestTypes = mutableListOf() + var closeCalls = 0 + + override val localChannelNumber: Int = 1 + override val remoteChannelNumber: Int = 2 + override val isOpen: Boolean get() = open + override val stdout: ReceiveChannel = Channel() + override val stderr: ReceiveChannel = Channel() + + fun enqueueRead(data: ByteArray) { + reads.trySend(data).getOrThrow() + } + + override suspend fun requestPty( + terminalType: String, + widthChars: Int, + heightRows: Int, + widthPixels: Int, + heightPixels: Int, + terminalModes: ByteArray, + ): Boolean = true + + override suspend fun resizeTerminal( + widthChars: Int, + heightRows: Int, + widthPixels: Int, + heightPixels: Int, + ): Boolean = true + + override suspend fun requestShell(): Boolean = true + + override suspend fun requestExec(command: String): Boolean = true + + override suspend fun requestSubsystem(name: String): Boolean = true + + override suspend fun write(data: ByteArray) { + writeFailure?.let { throw it } + writes++ + if (writes == 1) return + failAfterHandshake?.let { throw it } + + val body = ByteBuffer.wrap(data) + val length = body.int + val type = body.get().toInt() and 0xFF + val payload = ByteArray(length - 1) + body.get(payload) + val requestId = ByteBuffer.wrap(payload, 0, 4).int + val requestPayload = payload.copyOfRange(4, payload.size) + requestTypes += type + val response = responseFor(type, requestPayload) + enqueueRead(responsePacket(response.type, requestId, response.payload)) + } + + override suspend fun read(): ByteArray? = reads.receiveCatching().getOrNull() + + override suspend fun readExtended(): Pair? = null + + override suspend fun sendEof() = Unit + + override fun close() { + closeCalls++ + open = false + reads.close() + } + } + + private class FakeHandshakeTransport( + private val writeResult: SftpResult = SftpResult.Success(Unit), + private val readResult: SftpResult = SftpResult.Success( + SftpRawPacket(SSH_FXP_VERSION, ByteBuffer.allocate(4).putInt(3).array()), + ), + ) : SftpPacketTransport { + override suspend fun readPacket(): SftpResult = readResult + + override suspend fun writePacket(type: Int, payload: ByteArray): SftpResult = writeResult + } + + private companion object { + const val SSH_FXP_VERSION = 2 + const val SSH_FXP_OPEN = 3 + const val SSH_FXP_CLOSE = 4 + const val SSH_FXP_READ = 5 + const val SSH_FXP_WRITE = 6 + const val SSH_FXP_LSTAT = 7 + const val SSH_FXP_FSTAT = 8 + const val SSH_FXP_SETSTAT = 9 + const val SSH_FXP_FSETSTAT = 10 + const val SSH_FXP_OPENDIR = 11 + const val SSH_FXP_READDIR = 12 + const val SSH_FXP_REMOVE = 13 + const val SSH_FXP_MKDIR = 14 + const val SSH_FXP_RMDIR = 15 + const val SSH_FXP_REALPATH = 16 + const val SSH_FXP_STAT = 17 + const val SSH_FXP_RENAME = 18 + const val SSH_FXP_READLINK = 19 + const val SSH_FXP_SYMLINK = 20 + const val SSH_FXP_STATUS = 101 + const val SSH_FXP_HANDLE = 102 + const val SSH_FXP_DATA = 103 + const val SSH_FXP_NAME = 104 + const val SSH_FXP_ATTRS = 105 + } +} diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/sftp/SftpDispatcherTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/sftp/SftpDispatcherTest.kt new file mode 100644 index 0000000..40d5e2d --- /dev/null +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/sftp/SftpDispatcherTest.kt @@ -0,0 +1,231 @@ +/* + * ConnectBot SSH Library + * Copyright 2025-2026 Kenny Root + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.sshlib.client.sftp + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.async +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout +import org.connectbot.sshlib.SftpResult +import org.connectbot.sshlib.SftpStatusCode +import org.junit.jupiter.api.Test +import java.nio.ByteBuffer +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertTrue + +class SftpDispatcherTest { + + @Test + fun `request prepends request id and receives matching response`() = runBlocking { + val packetIO = FakePacketTransport() + val dispatcher = SftpDispatcher(packetIO) + dispatcher.startReadLoop(this) + + val response = async { dispatcher.request(10, byteArrayOf(7, 8)) } + val write = packetIO.awaitWrite() + val requestId = ByteBuffer.wrap(write.payload, 0, 4).int + + assertEquals(10, write.type) + assertContentEquals(byteArrayOf(7, 8), write.payload.copyOfRange(4, write.payload.size)) + + packetIO.enqueue(SftpResult.Success(packet(20, requestId, byteArrayOf(1, 2, 3)))) + + val result = assertIs>(response.await()).value + assertEquals(20, result.type) + assertContentEquals(byteArrayOf(1, 2, 3), result.payload) + + dispatcher.stop() + } + + @Test + fun `request returns write protocol server and io failures`() = runBlocking { + val io = IllegalStateException("no channel") + assertIs( + requestWithWriteResult(SftpResult.IoError(io)), + ) + + assertIs( + requestWithWriteResult(SftpResult.ProtocolError("bad packet")), + ) + + assertIs( + requestWithWriteResult(SftpResult.ServerError(SftpStatusCode.FAILURE, "server said no")), + ) + } + + @Test + fun `read loop skips malformed and unknown responses before matching response`() = runBlocking { + val packetIO = FakePacketTransport() + val dispatcher = SftpDispatcher(packetIO) + dispatcher.startReadLoop(this) + + val response = async { dispatcher.request(11, ByteArray(0)) } + val requestId = ByteBuffer.wrap(packetIO.awaitWrite().payload, 0, 4).int + + packetIO.enqueue(SftpResult.Success(SftpRawPacket(99, byteArrayOf(1, 2, 3)))) + packetIO.enqueue(SftpResult.Success(packet(99, requestId + 1, byteArrayOf(8)))) + packetIO.enqueue(SftpResult.Success(packet(99, requestId, byteArrayOf(9)))) + + val result = assertIs>(response.await()).value + assertContentEquals(byteArrayOf(9), result.payload) + + dispatcher.stop() + } + + @Test + fun `read loop protocol error completes pending request`() = runBlocking { + val packetIO = FakePacketTransport() + val dispatcher = SftpDispatcher(packetIO) + dispatcher.startReadLoop(this) + + val response = async { dispatcher.request(12, ByteArray(0)) } + packetIO.awaitWrite() + packetIO.enqueue(SftpResult.ProtocolError("short packet")) + + val result = assertIs(response.await()) + assertEquals("short packet", result.cause.message) + } + + @Test + fun `read loop io error completes pending request`() = runBlocking { + val packetIO = FakePacketTransport() + val dispatcher = SftpDispatcher(packetIO) + dispatcher.startReadLoop(this) + + val response = async { dispatcher.request(13, ByteArray(0)) } + packetIO.awaitWrite() + packetIO.enqueue(SftpResult.IoError(IllegalStateException("closed"))) + + val result = assertIs(response.await()) + assertEquals("closed", result.cause.message) + } + + @Test + fun `read loop server error exits and pending request times out`() = runBlocking { + val packetIO = FakePacketTransport() + val dispatcher = SftpDispatcher(packetIO) + dispatcher.startReadLoop(this) + + val response = async { dispatcher.request(14, ByteArray(0), timeoutMs = 50) } + packetIO.awaitWrite() + packetIO.enqueue(SftpResult.ServerError(SftpStatusCode.FAILURE, "framing server error")) + + assertIs(response.await()) + } + + @Test + fun `read loop unexpected exception completes pending request`() = runBlocking { + val packetIO = FakePacketTransport() + val dispatcher = SftpDispatcher(packetIO) + dispatcher.startReadLoop(this) + + val response = async { dispatcher.request(15, ByteArray(0)) } + packetIO.awaitWrite() + packetIO.enqueueThrow(IllegalArgumentException("boom")) + + val result = assertIs(response.await()) + assertEquals("boom", result.cause.message) + } + + @Test + fun `stop completes pending request`() = runBlocking { + val packetIO = FakePacketTransport() + val dispatcher = SftpDispatcher(packetIO) + + val response = async { dispatcher.request(16, ByteArray(0)) } + packetIO.awaitWrite() + dispatcher.stop() + + val result = assertIs(response.await()) + assertEquals("SFTP session closed", result.cause.message) + } + + @Test + fun `writeRaw and readRaw forward packet io results`() = runBlocking { + val packetIO = FakePacketTransport() + val dispatcher = SftpDispatcher(packetIO) + packetIO.enqueue(SftpResult.Success(SftpRawPacket(2, byteArrayOf(3)))) + + assertEquals(SftpResult.Success(Unit), dispatcher.writeRaw(1, byteArrayOf(2))) + assertEquals(SftpResult.Success(SftpRawPacket(2, byteArrayOf(3))), dispatcher.readRaw()) + assertEquals(Write(1, byteArrayOf(2)), packetIO.writes.single()) + } + + private suspend fun requestWithWriteResult(writeResult: SftpResult): SftpResult { + val packetIO = FakePacketTransport() + packetIO.writeResult = writeResult + return SftpDispatcher(packetIO).request(1, ByteArray(0)) + } + + private fun packet(type: Int, requestId: Int, payload: ByteArray): SftpRawPacket { + val fullPayload = ByteBuffer.allocate(4 + payload.size) + .putInt(requestId) + .put(payload) + .array() + return SftpRawPacket(type, fullPayload) + } + + private data class Write(val type: Int, val payload: ByteArray) { + override fun equals(other: Any?): Boolean = other is Write && type == other.type && payload.contentEquals(other.payload) + + override fun hashCode(): Int = 31 * type + payload.contentHashCode() + } + + private sealed interface ReadEvent { + data class Packet(val result: SftpResult) : ReadEvent + data class Throw(val cause: Throwable) : ReadEvent + } + + private class FakePacketTransport : SftpPacketTransport { + private val readEvents = Channel(Channel.UNLIMITED) + private val firstWrite = CompletableDeferred() + val writes = mutableListOf() + var writeResult: SftpResult = SftpResult.Success(Unit) + + suspend fun awaitWrite(): Write = withTimeout(1_000) { + while (!firstWrite.isCompleted) { + delay(1) + } + firstWrite.await() + } + + fun enqueue(result: SftpResult) { + assertTrue(readEvents.trySend(ReadEvent.Packet(result)).isSuccess) + } + + fun enqueueThrow(cause: Throwable) { + assertTrue(readEvents.trySend(ReadEvent.Throw(cause)).isSuccess) + } + + override suspend fun readPacket(): SftpResult = when (val event = readEvents.receive()) { + is ReadEvent.Packet -> event.result + is ReadEvent.Throw -> throw event.cause + } + + override suspend fun writePacket(type: Int, payload: ByteArray): SftpResult { + val write = Write(type, payload) + writes += write + firstWrite.complete(write) + return writeResult + } + } +} diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/sftp/SftpPacketIOTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/sftp/SftpPacketIOTest.kt new file mode 100644 index 0000000..5e5ffde --- /dev/null +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/sftp/SftpPacketIOTest.kt @@ -0,0 +1,141 @@ +/* + * ConnectBot SSH Library + * Copyright 2025-2026 Kenny Root + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.sshlib.client.sftp + +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.runBlocking +import org.connectbot.sshlib.SftpResult +import org.connectbot.sshlib.SshSession +import org.junit.jupiter.api.Test +import java.nio.ByteBuffer +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertIs + +class SftpPacketIOTest { + + @Test + fun `readPacket rejects invalid lengths`() = runBlocking { + val tooSmall = FakeSshSession() + tooSmall.enqueue(ByteBuffer.allocate(4).putInt(0).array()) + assertIs(SftpPacketIO(tooSmall).readPacket()) + + val tooLarge = FakeSshSession() + tooLarge.enqueue(ByteBuffer.allocate(4).putInt(256 * 1024 + 1).array()) + assertIs(SftpPacketIO(tooLarge).readPacket()) + } + + @Test + fun `readPacket reports closed channel before packet completes`() = runBlocking { + val session = FakeSshSession() + session.enqueue(ByteBuffer.allocate(4).putInt(4).array()) + session.closeReads() + + val result = SftpPacketIO(session).readPacket() + + assertIs(result) + assertEquals("ChannelClosedException", result.cause::class.simpleName) + } + + @Test + fun `readPacket buffers extra bytes for following packet`() = runBlocking { + val session = FakeSshSession() + session.enqueue(packet(10, byteArrayOf(1)) + packet(11, byteArrayOf(2, 3))) + val packetIO = SftpPacketIO(session) + + val first = assertIs>(packetIO.readPacket()).value + val second = assertIs>(packetIO.readPacket()).value + + assertEquals(10, first.type) + assertContentEquals(byteArrayOf(1), first.payload) + assertEquals(11, second.type) + assertContentEquals(byteArrayOf(2, 3), second.payload) + } + + @Test + fun `writePacket serializes length type and payload`() = runBlocking { + val session = FakeSshSession() + + val result = SftpPacketIO(session).writePacket(99, byteArrayOf(1, 2, 3)) + + assertEquals(SftpResult.Success(Unit), result) + assertContentEquals(byteArrayOf(0, 0, 0, 4, 99, 1, 2, 3), session.writes.single()) + } + + private fun packet(type: Int, payload: ByteArray): ByteArray { + val packet = ByteBuffer.allocate(4 + 1 + payload.size) + packet.putInt(1 + payload.size) + packet.put(type.toByte()) + packet.put(payload) + return packet.array() + } + + private class FakeSshSession : SshSession { + private val reads = Channel(Channel.UNLIMITED) + val writes = mutableListOf() + + override val localChannelNumber: Int = 1 + override val remoteChannelNumber: Int = 2 + override val isOpen: Boolean = true + override val stdout: ReceiveChannel = Channel() + override val stderr: ReceiveChannel = Channel() + + fun enqueue(data: ByteArray) { + reads.trySend(data).getOrThrow() + } + + fun closeReads() { + reads.close() + } + + override suspend fun requestPty( + terminalType: String, + widthChars: Int, + heightRows: Int, + widthPixels: Int, + heightPixels: Int, + terminalModes: ByteArray, + ): Boolean = true + + override suspend fun resizeTerminal( + widthChars: Int, + heightRows: Int, + widthPixels: Int, + heightPixels: Int, + ): Boolean = true + + override suspend fun requestShell(): Boolean = true + + override suspend fun requestExec(command: String): Boolean = true + + override suspend fun requestSubsystem(name: String): Boolean = true + + override suspend fun write(data: ByteArray) { + writes += data + } + + override suspend fun read(): ByteArray? = reads.receiveCatching().getOrNull() + + override suspend fun readExtended(): Pair? = null + + override suspend fun sendEof() = Unit + + override fun close() = Unit + } +} diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/AdditionalCoverageTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/AdditionalCoverageTest.kt new file mode 100644 index 0000000..d8b4e39 --- /dev/null +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/AdditionalCoverageTest.kt @@ -0,0 +1,82 @@ +/* + * ConnectBot SSH Library + * Copyright 2026 Kenny Root + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.sshlib.crypto + +import org.connectbot.sshlib.SshException +import org.junit.jupiter.api.Test +import java.io.IOException +import java.security.PublicKey +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse + +class AdditionalCoverageTest { + + @Test + fun `JavaMlKemProvider wraps and validates raw ML-KEM public keys`() { + val rawKey = ByteArray(1184) { (it and 0xff).toByte() } + + val wrapped = JavaMlKemProvider.wrapRawMlKemPublicKey(rawKey) + assertEquals(1206, wrapped.size) + assertContentEquals(rawKey, JavaMlKemProvider.extractRawMlKemPublicKey(wrapped)) + + assertFailsWith { + JavaMlKemProvider.wrapRawMlKemPublicKey(ByteArray(32)) + } + assertFailsWith { + JavaMlKemProvider.extractRawMlKemPublicKey(ByteArray(4)) + } + assertFailsWith { + JavaMlKemProvider.extractRawMlKemPublicKey(wrapped.copyOf().also { it[0] = 0x31 }) + } + assertFailsWith { + JavaMlKemProvider.extractRawMlKemPublicKey(wrapped.copyOf().also { it[17] = 0x04 }) + } + assertFailsWith { + JavaMlKemProvider.extractRawMlKemPublicKey(wrapped.copyOf().also { it[21] = 0x01 }) + } + assertFailsWith { + JavaMlKemProvider.extractRawMlKemPublicKey(wrapped.copyOf(32)) + } + } + + @Test + fun `AesCtrCipher rejects invalid key and iv sizes`() { + assertFailsWith { + AesCtrCipher(ByteArray(15), ByteArray(16), forEncryption = true) + } + assertFailsWith { + AesCtrCipher(ByteArray(16), ByteArray(15), forEncryption = true) + } + } + + @Test + fun `inferKeyType rejects unsupported public keys`() { + val key = object : PublicKey { + override fun getAlgorithm(): String = "Unsupported" + override fun getFormat(): String = "X.509" + override fun getEncoded(): ByteArray = byteArrayOf(1, 2, 3) + } + + assertFalse(isEd25519Key(key)) + assertFailsWith { + inferKeyType(key) + } + } +} diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/KeyEncryptionTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/KeyEncryptionTest.kt new file mode 100644 index 0000000..5d3be4d --- /dev/null +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/KeyEncryptionTest.kt @@ -0,0 +1,83 @@ +/* + * ConnectBot SSH Library + * Copyright 2025-2026 Kenny Root + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.sshlib.crypto + +import org.connectbot.sshlib.SshException +import org.junit.jupiter.api.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class KeyEncryptionTest { + + @Test + fun `encryptPem supports DES and AES key sizes`() { + val password = "secret".toByteArray() + val plaintext = "PEM payload".toByteArray(Charsets.ISO_8859_1) + + for (cipherName in listOf("DES-CBC", "DES-EDE3-CBC")) { + val salt = ByteArray(8) { (it + 1).toByte() } + val encrypted = KeyEncryption.encryptPem(plaintext, password, salt, cipherName) + assertContentEquals(plaintext, KeyDecryption.decryptPem(encrypted, password, salt, cipherName)) + } + + for (cipherName in listOf("AES-192-CBC")) { + val salt = ByteArray(16) { (it + 1).toByte() } + val encrypted = KeyEncryption.encryptPem(plaintext, password, salt, cipherName) + assertContentEquals(plaintext, KeyDecryption.decryptPem(encrypted, password, salt, cipherName)) + } + } + + @Test + fun `encryptPem rejects unsupported cipher`() { + assertFailsWith { + KeyEncryption.encryptPem(byteArrayOf(1), byteArrayOf(2), ByteArray(16), "AES-512-CBC") + } + } + + @Test + fun `encryptOpenSsh roundtrips supported ciphers`() { + val password = "secret".toByteArray() + val salt = ByteArray(16) { (it * 3).toByte() } + val plaintext = ByteArray(32) { it.toByte() } + + for (cipherName in listOf("aes256-ctr", "aes256-cbc", "aes128-ctr", "aes128-cbc")) { + val encrypted = KeyEncryption.encryptOpenSsh(plaintext, password, salt, 8, cipherName) + assertContentEquals(plaintext, KeyDecryption.decryptOpenSsh(encrypted, password, salt, 8, cipherName)) + } + } + + @Test + fun `encryptOpenSsh rejects unsupported cipher`() { + assertFailsWith { + KeyEncryption.encryptOpenSsh(byteArrayOf(1), byteArrayOf(2), ByteArray(16), 4, "aes192-ctr") + } + } + + @Test + fun `addPkcs7Padding adds a full block when already aligned`() { + val padded = KeyEncryption.addPkcs7Padding(byteArrayOf(1, 2, 3, 4), 4) + + assertContentEquals(byteArrayOf(1, 2, 3, 4, 4, 4, 4, 4), padded) + } + + @Test + fun `byteArrayToHex uses uppercase two character bytes`() { + assertEquals("000FA5FF", KeyEncryption.byteArrayToHex(byteArrayOf(0x00, 0x0F, 0xA5.toByte(), 0xFF.toByte()))) + } +} diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/PacketPrimitiveInterfacesTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/PacketPrimitiveInterfacesTest.kt new file mode 100644 index 0000000..f018cc6 --- /dev/null +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/PacketPrimitiveInterfacesTest.kt @@ -0,0 +1,79 @@ +/* + * ConnectBot SSH Library + * Copyright 2025-2026 Kenny Root + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.sshlib.crypto + +import org.junit.jupiter.api.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse + +class PacketPrimitiveInterfacesTest { + + @Test + fun `PacketCipher default destroy state is false`() { + val cipher = object : PacketCipher { + override val blockSize: Int = 8 + override fun encrypt(data: ByteArray): ByteArray = data + override fun decrypt(data: ByteArray): ByteArray = data + } + + cipher.destroy() + + assertFalse(cipher.isDestroyed) + assertEquals(8, cipher.blockSize) + assertContentEquals(byteArrayOf(1), cipher.encrypt(byteArrayOf(1))) + assertContentEquals(byteArrayOf(2), cipher.decrypt(byteArrayOf(2))) + } + + @Test + fun `PacketMac default destroy state is false`() { + val mac = object : PacketMac { + override val macLength: Int = 4 + override fun compute(sequenceNumber: Long, packet: ByteArray): ByteArray = byteArrayOf(sequenceNumber.toByte()) + override fun computeEtm(sequenceNumber: Long, packetLength: Int, encryptedPayload: ByteArray): ByteArray = byteArrayOf(packetLength.toByte()) + } + + mac.destroy() + + assertFalse(mac.isDestroyed) + assertEquals(4, mac.macLength) + assertContentEquals(byteArrayOf(3), mac.compute(3, byteArrayOf())) + assertContentEquals(byteArrayOf(9), mac.computeEtm(3, 9, byteArrayOf())) + } + + @Test + fun `PacketAead defaults do not encrypt length`() { + val aead = object : PacketAead { + override val tagLength: Int = 16 + override fun encrypt(packetLength: ByteArray, plaintext: ByteArray): AeadResult = AeadResult(plaintext, packetLength) + + override fun decrypt(packetLength: ByteArray, ciphertext: ByteArray, tag: ByteArray): ByteArray = ciphertext + } + + aead.destroy() + + assertFalse(aead.isDestroyed) + assertFalse(aead.encryptsLength) + assertEquals(16, aead.tagLength) + assertContentEquals(byteArrayOf(1), aead.encrypt(byteArrayOf(2), byteArrayOf(1)).ciphertext) + assertContentEquals(byteArrayOf(3), aead.decrypt(byteArrayOf(2), byteArrayOf(3), byteArrayOf(4))) + assertFailsWith { aead.encryptLength(0, byteArrayOf()) } + assertFailsWith { aead.decryptLength(0, byteArrayOf()) } + } +} diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/transport/TransportDependenciesTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/transport/TransportDependenciesTest.kt new file mode 100644 index 0000000..d7aecb5 --- /dev/null +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/transport/TransportDependenciesTest.kt @@ -0,0 +1,32 @@ +/* + * ConnectBot SSH Library + * Copyright 2026 Kenny Root + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot.sshlib.transport + +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import kotlin.test.assertTrue + +class TransportDependenciesTest { + + @Test + fun `default resolver resolves localhost`() = runTest { + val addresses = DefaultAddressResolver().resolve("localhost") + + assertTrue(addresses.isNotEmpty()) + } +} From ef2cb278fe5824fcce2ea5f175c70eade09d5111 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Wed, 20 May 2026 23:24:22 -0700 Subject: [PATCH 5/6] chore: enable verification for gradle deps --- .gitignore | 4 + gradle.properties | 3 + gradle/verification-keyring.keys | 5001 ++++++++++++++++++++++++++++++ gradle/verification-metadata.xml | 422 +++ 4 files changed, 5430 insertions(+) create mode 100644 gradle/verification-keyring.keys create mode 100644 gradle/verification-metadata.xml diff --git a/.gitignore b/.gitignore index 9f1237d..703c72a 100644 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,7 @@ local.properties # Git worktrees .worktrees/ + +# Gradle dependency verification caches and backups +gradle/verification-keyring.gpg +gradle/verification-keyring.keys.bak diff --git a/gradle.properties b/gradle.properties index 5eb08c4..73c68ca 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,6 @@ +org.gradle.caching=true +org.gradle.parallel=true +org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=2g org.gradle.configuration-cache=false #org.gradle.configuration-cache.parallel=true diff --git a/gradle/verification-keyring.keys b/gradle/verification-keyring.keys new file mode 100644 index 0000000..1281e61 --- /dev/null +++ b/gradle/verification-keyring.keys @@ -0,0 +1,5001 @@ +pub 84E913A8E3A748C0 +uid The Legion of the Bouncy Castle Inc. (Maven Repository Artifact Signer) + +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBGR/8HUBDADJ+V5VgTXFG4xVI/1r07a/pTXoAQhHyJMkVdFScGARsps07VXI +IsYgPsifOFU55E7uRMZPTLAx5F1uxoZAWGtXIz0d4ISKhobFquH8jZe7TnsJBJNV +eo3u7G54iSfLifiJ4q17NvaESBNSirPaAPfEni93+gQvdn3zVnDPfO+mhO00l/fE +5GnqHt/Q2z2WKVQt3Vg0R66phe2XaFnycY/d+an73FiXqhuhm4sXlcA++gfSt1H1 +K7+ApqJsX9yw79A1FlGTPOeimqZqE75+OyQ9Kz0XTvN/GmHeEygTrNEnMDTr1BWz +P0/ut0UXmktJtJXgLi5wUCncwwi+UpCSwwou7/3r+eBh5aykxSo9OtYe4xPNKWSo +EiPZXpCH5Wjq9TpXOuhnZvRFqbR24mWz5+J/DoaVP3pwEhGXxr5VjVc1f8gJ8A34 +YYPlxUGcl8f3kykzvl4X5HDIbHb9MAl+9qtwQo1tFA9umD2Da/8bSsxrnZdkkzEA +OpJYwT1EkQRZRcUAEQEAAbRmVGhlIExlZ2lvbiBvZiB0aGUgQm91bmN5IENhc3Rs +ZSBJbmMuIChNYXZlbiBSZXBvc2l0b3J5IEFydGlmYWN0IFNpZ25lcikgPGJjbWF2 +ZW5zeW5jQGJvdW5jeWNhc3RsZS5vcmc+ +=ScYI +-----END PGP PUBLIC KEY BLOCK----- + +pub 854D0560FEF4601F +uid gpg --full-generate-key + +sub 3E6AB50FEE7B3A77 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xjMEZaTU3hYJKwYBBAHaRw8BAQdAwHz1/IYduRmueigliJWI/vKp+d+Tjiukh4Co +6OK3ZZS0L2dwZyAtLWZ1bGwtZ2VuZXJhdGUta2V5IDxyb25ob21icmUxNkBnbWFp +bC5jb20+zjgEZaTU3hIKKwYBBAGXVQEFAQEHQCXebBbvYTtFeDX6tD3AaqwrHyCv +Q0YSopH+9Spom9AuAwEIB8J+BBgWCgAmFiEEezgdGWunTJ+nzu6EhU0FYP70YB8F +AmWk1N4CGwwFCQWjmoAACgkQhU0FYP70YB8p5gEAlG6CS8b3v8b1miK8Nl3lwSvC +A21VWBvvuurjic24i6sBAJgpWXAqEIqQLpxKje/OGemGDTmD3iXOoXWw5JjudzEI +=ip/v +-----END PGP PUBLIC KEY BLOCK----- + +pub 85911F425EC61B51 +uid Marc Philipp + +sub 8B2A34A7D4A9B8B3 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +PXCe8La72ukmaxEGbpJ8mdzS2PJko7mm +=Wfvg +-----END PGP PUBLIC KEY BLOCK----- + +pub 86FDC7E2A11262CB +uid Gary David Gregory (Code signing key) + +sub 59BA7BFEAD3D7F94 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBE2kzuwBCACYV+G9yxNkSjAKSji0B5ipMGM74JAL1Ogtcu+993pLHHYsdXri +WWXi37x9PLjeHxw63mN26SFyrbMJ4A8erLB03PDjw0DEzAwiu9P2vSvL/RFxGBbk +cM0BTNXNR1rk8DpIzvXtejp8IHtD1qcDLTlJ8D0W3USebShDPo6NmMxTNuH0u99B +WHCMAdSa34wsg0ZpffwQmRxeA+ebrf2ydKupGkeZsKjkLlaXNkTVp1ghn5ts/lvg +KeHv1SJivWKCRmFlbPhBK4+mxSUSOPdoBNAfxA51QzZoPizSk0VbRz3YufYRVLFy +9vqPSorDmYJhCvn3f6+A38FS/j8VE+8obQ2rABEBAAG0O0dhcnkgRGF2aWQgR3Jl +Z29yeSAoQ29kZSBzaWduaW5nIGtleSkgPGdncmVnb3J5QGFwYWNoZS5vcmc+zsBN +BE2kzuwBCACzeGpkd6X/xTfKDBWvXgHOOKIJ2pht9XmtZZKiIj7LIiSwvSds/Zko +ZKxAm7AY+KPh8Xjf968FtoUBQJvHAG4rbowEqT7OOrJae2JcenH5qzaod7TpIPQV +v+Ysz8I1wLlC6LzKRj1X99Hng6X+obsEasnPbmEEkuiZ/Sgi4vVC8SHkDmYt1Dx8 +jDgm53oUeWkEJO9LSI2zcrZhSgvg1xa4Q4gY5UUK7gE4LbmGCjFlATuuW/0sryxu +8zxph15gkn4Nqgk0CPMSjesMYEGOsdDzfQXl2tXbt+Pe6mBoWh67MZ1v5zOq3EDt +oSqDpWPxponAeaCuNDDFX44vGjfxGE0tABEBAAHCwF8EGAECAAkFAk2kzuwCGwwA +CgkQhv3H4qESYsvEMAf/VGyqIEcw4T2D3gZZ3ITkeoBevQdxBT/27xNvoWOZyGSz +GYlRbRQrlo+uZsjfMc9MNvaSmxyy4gLVbcdvQr3PF//GxphJ98W8pk9l+M57jfyH +nnCumn7MO4o9ed+WuigN5oeuNJ6BIq3ff2o1DsrEvDChYOJEOeFuWxv+u7I2ABJJ +ep7NbByM2n9PE8vlGU3zUBgWUBsk6jT+klKnEyHE76WzegPLz3jtElTuyB7jRhjy +QJu1yiJEMbs2zH8aJGObi5f8Jum4tILZuEAdoI0M3c3VRq12cz/vLy+9VXa/s//8 +IsGn88kjyyYqOy8WJEjoOXFh++dpWiM7nZkgQcNi5A== +=ewZO +-----END PGP PUBLIC KEY BLOCK----- + +pub 873A8E86B4372146 +uid Olivier Lamy + +sub 1AFEC329B615D06C +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDiBEdddbQRBADRgstdUZq7ceq3NYcR5kpoU2tN2Zvg1vptE9FxpDbL73gdLWnI +C7IAx+NNjdG7Ncdg+u10UZv6OSmhWAd8ubWcD9JxKtS4UXkNPHxhHFHqVPHuCwsQ +q2AaCtuOk6q9OtthQX6LfOuGqwbv9uH/KLUDn91PrgKuHPVfVveiF30ZvwCggutX +D0jTGRHzUJl7F1wViuckHJcD/2z76t0ObSuTnENi0IUjF3Toe4tv+qO+Ljs0knvK +tu1b8A5Bs+kxNcbEqV+zdIph+6gCL9jy+dB9J+t6uZg6ACJexbIkDPsutNtbAVDV +w5AtM7JR8930dRHfEt26ahFohFi+73V8RiA7LrmMjA8rX4zuo5Pr48xt/RR1Y/VE +8ohCA/wOqul9eHHevxeEMDYoGVjGl2EiuIThg4eYuQDDSisBNb9a6dhE8ECQFFBx +mGz32+I8gXSTKFAkkQUI4HmJmTX35nGJql6E7Bn5yM2OaOG04PV+xkhScJll5ZxZ +BNEccFDL/aI4N33cwrLHyk+wFNZHBL1hnHpxpjFZYv5xfEBjmbQfT2xpdmllciBM +YW15IDxvbGFteUBhcGFjaGUub3JnPs7BTQRHXXXPEAgAyqEz3eBEKiZ7VbAj96Ht +IvGufKTdZ0ERJtrdPO4FUGVBcXpphtnPn+JOWomszUKkKLO4x24OaDCG/SENsPy+ +Ned4wjBB+4uV0YEc5Xn8gts3g4Z5p+YiVu+aWeYPPC5BPU61tVqc996i9ZYkZiYO +s9F5Z+dKozk3KwVcijaCr0IQMjAtJ/N70zcciP23KhrN9Z3Nn54Xm7GezD0nxTUG +P8gM79zKHnVhDBptrxIT/adCzU9/UX3UVAQcdq86FfzTEpqFG3TM75HBTQgHihIk +kirzurE+ivh6aaF3UJwmDBe5Wu3gvxF6Rl0Ja/YBNkkCiOXngXSxwvUUR8KJO07R +GwADBggAxOFV2DfMHsTBu++gKJ94L6VjETfVFEYPo7e4tO2Zn2Unzdxz2BoTJcQY +0j6/M3Tl9hCwhOSVVL8Ao/wp1ykjgXnwV4vz0be4d/ZML+KF15x+8730H7Th+aR+ +Ug6K6Khsp8XIypmLJcYgYLD02PlSnDxCq9Fbv0JDlbr6tbsJiVzoRjg+WNEIB3II +rJbTIiOFrRBhloinYoot216QJ1rI2nQpMEBlSuX6f4jYF6F7X4dAY4V4ohjFeJCb +6SYkKbj4caqBA9OVrj3vh8v/vAUKDB8pqVhpaZicFpMd2pEEYVMEU4i1sLE3X73y +9RRuaJOvPAx2HHT8MlWjsDmNdY2Mg8JgBBgRAgAJBQJHXXXPAhsMACEJEIc6joa0 +NyFGFiEE8lSzVhfcJV2TRLz6hzqOhrQ3IUZkrACcDtB3CttHYgGrF31KCocX/gwC +iYEAn18D875QwNruE4Qkt/W0Rhy/QRIZ +=3ecU +-----END PGP PUBLIC KEY BLOCK----- + +pub 991EFB94DB91127D +uid Antoine Mottier + +sub C327DD2B96A50E1C +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +36KJDm9B2wR+2joiMeA9BoBqpmLY5nvNcVheJw== +=Bxk9 +-----END PGP PUBLIC KEY BLOCK----- + +pub 995EFBF4A3D20BEB +uid Ktlint (ktlint signing key) + +sub B89991D171A02F5C +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +bZRG +=N23Z +-----END PGP PUBLIC KEY BLOCK----- + +pub 9AEE152CDCCEBFCB +uid Hakan Altindag + +sub 49A09601D2948101 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsFNBF0LJ4UBEADDviIvloIaEtjAac3EHGGQtHtqKlp4uXXIgEWh1vNulzVSRpBE +LDqDcrTowNU+CYQ3elTKa8cDrZviR7bMBju7esbVWxZu3ueMTG3IrvbDZUPYQ9Zt +DQQr4/kDaSn/JpNiOyC815QHC5eQD9CjIRntZnxiigpIerb2PStd2v7DcziA9oK9 +8ByIVvEAWjxawI/E/Nkt1kuasoQCvdcZrhoGhPvLzI4OdCpgow8IB8kpRlO7vZGF +ncQXuyluA2FXO0t1FHOGaPN5F+PZ0JuBH/84VWacepSO0lztopMpUtzS3eNzxUDq +t01Q35RPgzp/DAh4lAB03XA6vo2BDzMG61CxieH8Qd+7lqXglO6y376gtuQ3H0Hk +HoXLRn/0mExcYRxAR5li+Loib65da9nRGclIhYz5Ksy9waqzkSIU16UX/xmxo0S7 +T7OFhKexoRPsJkNPSFRgdj/Kro03WL7qqPMemJ5tjAcfbIDcI1HJH3uTmK6tlDfG +L62Rz8LskiVjHXhLShq5KgGRB+Z6o2aW8tjy3RqBGJDRmh1pqOok3VgvfohiYukN +VgK7oEJflq44v2ZW2T+/06iPX073TpcxGmpUKBkh4EybO6v1Crucb4+6L6c32xS0 ++DDz0tw4xm320iPthut6xlaAjaUvP0BKxwrzwifImTeZUx2p/5ewydMElQARAQAB +tCpIYWthbiBBbHRpbmRhZyA8aGFrYW5nb3VkYmVyZ0Bob3RtYWlsLmNvbT7OwU0E +XQsnhQEQALfG1xMZs+T9N0zrC7InpLCj2N2aBIARoScyJYwNPjLpQnk+mGsEsT0l +b1Q7nyJRjHdrLhJcKNedrBQ0Ro5o13IzibwDyi3ju0RTsBZsf7IWtI/gv12WjmU3 +Y3/DeIyyTWp9GYuk/g8fUFBUCEZmroKgoepnfmhOqQbQ1RS+I3Za7+wky5oymxLP +F2ifIvx7OvYW6GJrzC2XoJSVLbPnP11gKdoD9LCohkO7IWHwhC+GdxLt+S4/iw8X +f+3Bg80gKS/cpsq9hZ6WvVGVFwgC07ikWxkAvugyhyfUOBCjKzpCQfN3B9vG0Utj +zeH4CXz2FDv0rqSwGYtGOgbPtQYn9o9vX8QMhvHoJU+2PJ7lm1PCKBuaCkMMcrxq +O1TXllE0YP7rom3LxiXkBlh4j34na8kPpE8Zrjkn1Iu7QVboETnxiN2NkmE9nayY +JYecU0Bo0dkVNhNHxnPxBHVSuaQW5PsQHmUSInGsKH9YeQiSRWJX8EMh9H9WLXq2 +uzBuSKXPndGrH/y67x1BbbN9bq7MSKhRrqQ1RX2rTLVwl3puRN7cgxo1P+0TrF7d +gyjvzHhuaUl1vZjm9qN6xOSwA0cdHFhjWbcSjXWPUFhbRbKlQ37/w9iKUiOnL/Z5 +qAQNp4M8MeUjaD1jiDUb9ketxUbt43iHHVhAru4nsKilMYMfyp5BABEBAAHCwXYE +GAEIACAWIQTlE4qOny5+QtOLFNma7hUs3M6/ywUCXQsnhQIbDAAKCRCa7hUs3M6/ +y+cJEACTa3ag+4vVdxkoQlSmXqxmbJhKFMcXvFxl05VQYmBvIvymuJm9lggAr6ln +28RZg0xXHQSt1UV3bQyQjKEYdGWWzYoez+5l/Voe9zvdsayAVTDwnesbV9c8Cta9 +duzn2UvVPIV6okNP+GSpqH1+HSSScBZcmb1wuB2UgE310mmJEMLY0Nrguizctvjh +uQdBmFjH/mlHgB5bEEbPjkBf9e3A3hy0+UGmb76ztf+00UNmAutHJdG1DsLYlGEU +64voM5ONLlxjXFwTBT5zdS8ZB0eaGPq+P97Lzgj8Oq3VdNBONFMUazX1ItM92hbJ +u/F00TB1onSJ6c5EXXPzRVbF2lmXp/P/gcRBrpi3Vxmt4GUTxQnImkUzPmfJ5e7M +U9MiWpoqcqaBN+ru0gGeA0bC1ifbEQM8uSEll/Vpkp4l4XAa4oHr4VoVrcn0TGjC +tQoLZkd97Uf7BsURTXQw8FjGzBxRgja7B8FBugKaoWZQTwyfOIS84zb4NCbOk8hb +wtZuRjSadFsEOeRwXZnX+6iNjiJMznRbvms80mBeuFD4N4oMtMSrE9dECpRJIMVL +KGewWdpjXv8kFjDVklakmq4O2YCOZ/uk9wvr2qSAH04hnRQo7kHraRvY3qRP2Iii +n6cA3cpY/exTwltLUYewv5ddlxsvArPkyxKptL2TBA6B4Ce8UA== +=NFcC +-----END PGP PUBLIC KEY BLOCK----- + +pub 9EB80E92EB2135B1 +uid Slawomir Jaranowski + +sub E3F6790A5A167F5A +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=RJbM +-----END PGP PUBLIC KEY BLOCK----- + +pub A0C56CE5FB6663C0 +sub D47D87B7B735CAC5 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=A0dM +-----END PGP PUBLIC KEY BLOCK----- + +pub A1AE06236CA2BA62 +uid DiffPlug LLC + +sub 030DD9087C31C9AF +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBGPAYVUBDADCs9PkY8zzhzE38bRZX+vTrr3LdChGNBmObV858NGRrXeZtyLu +U7YwYVF7w2vHUI/JQWrlPZc5tBFicefucfUtKt790WzAk3NBjGtX4IWpG57C1Z5t +QRI23HqWr1If0UanH2vUjy4fUgNZdYlH2KzookoU0950dIcqwA/HwiosO5RmQ9iY +HztkwwvFW9QxiJgL+lR84EcaIVN1ukr0ZKrG1a6wOJ6HLf9S2F3DMe7fQ+O+TpT/ +A11RewOZHwE9spH8cEsNYgutBouw/MttuYHjZKD7O4hN12MmzecWeMAPyrvYgTJp +PHcjQaVeD27OGLRpy5n5LonvkzJbhTzM+Ps1qEj/4of3EFVhxNjR0gdrkX+0Ub1A +XXiw3gjqAQuLQVKIbwvCbfj2go+YHrfcXN6kpKkYZU8ERPxI/VBTw230PPdXXwXP +Ux/JRQIEXOuMIuELm/91H8TXKutw6NdNRu5q+LPcUkU7W6jsv28dmQHdC+ebVHlA +vNcBOWVSY5e8IasAEQEAAbQjRGlmZlBsdWcgTExDIDxzc2xjZXJ0QGRpZmZwbHVn +LmNvbT7OwM0EY8BhVQEMAL4ZVuEVH9zbhY6AewA4T3u2XZ7k1KGOxoK74eygzYEp +fKMplWQtCxcxBXe2tboT7I8U3MrV6m7KDwcxLNVZM638fvfU3Px0yGs0jBzyjOcb +Vk6n18xX9UoNyoEqpxHhyPbTBr+U9OINcneXZ/iG9FfLURZjDxhNcQcnrnmvbUB6 +M+teZ6Gpb1Ye8ghVCJJNjRRQhFxXE7XnmX3C1pZoSoGcBx5zVspSuHjq7nTTw/rd +7OpC6sBK0ULk8GPAd2vJUfOtZcsLvOs2++bHxNULTXraTy/fYvXsTSe+PmbJo1Fd +5o3imI0eUy853UJmF/HbuWspFe8yONHjo0+uZITsAMq0jbzG8MTKMmgsXS/i/vaW +8BVUmLfhB7E+bUXJLJuQAan397NbYZqPF5agLZ1wHSki3iuYEttdMsy5PYCLeCqh +8Tv6VBNkEToKDAvNbaad4ZgBdwbBQaAIrWekWpiXSXizyGr/VTFE2hT5NC0d1BXy +sc9P2UCvHit6A1bCl7MSywARAQABwsD8BBgBCAAmFiEER5e09dzEbOphBZBxoa4G +I2yiumIFAmPAYVUCGwwFCQPCZwAACgkQoa4GI2yiumIHXwwAh4/tSXSQ9Btws4ZP +eLfihAb4ogHOsrJ8ZO+lZMyQOrEyzDK/y/1LpFVlHYEP51XS5h4u4XVivXGzsZ+r +tQoXaCS6n19dyyNeusehZx/BxxQrdV9OYEkgb3BC+05AWogdHXTP4prGdMtpSttd +gcxTuHwx9RUv/d6CsQ8DyKyjTv82hd3yuXQVl1829NwDbM7HJ8eq0uZPmez2ewbx +Ze9CxjKoOLfYSQ4k0DfcIFqz8CSqTVIz5aNLLXiY6NXPhS9B9/bXkRNAXzUgMrG4 +GmmP8XLYjBn9g8V+fAad67N0dUWDeAPzz3OXjp6bxyScgjT6OMlp55xXaE5HWW4a +aE9epjKjLuOD7LYdmv0GI1HhSrOnlqznB3TCwJgKMw6/37uGZnpsX0JoMs947ZIm +pcN1kNNR3e4aAFcpBwj2OSjds+G/DI3/WOXJj3aaRI4nBRr2/IB3TVhzLOizLTNQ +Q/IKL5Iy5doINK/iyjb/G/JLH1/TkhW9zEheiKUY6TiXeR3p +=fdmt +-----END PGP PUBLIC KEY BLOCK----- + +pub A41F13C999945293 +sub 8183E80D264EE073 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=+HbN +-----END PGP PUBLIC KEY BLOCK----- + +pub A6DCA467BAE34C82 +uid Mikhail Fedotov + +sub 091C9668C4EF6DDB +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBGLO8lwBDAC0L8c/XiwCTYkct9E3CjafWw0gEnBxZZPjOR4s4+mF6kULRUS9 +Q5cWrEn1XRcaKjPzfq51/Y60PWonQrd5JRMgh7wkQ1XDeUOBSe+EwKePG2rQWcxK +6AZ7YLyCTksKz6UCsGeM8hb5rV4ongTQu9s9TzdGF1iOuhszapPd90xHggQPu5r9 +jcRl1zvY4lp0J++GuvtxZdIGGFeH3XE7Vdv3MlNKExmmUGBfqaFbL5vNDmXeesVC +8XcgWNnUUNzFH15s9T/6PFd1eBx238A19wzeCtwFGsa9Yoqb3BrAdv17XSiEUa2m +s8Y6CjNIBTo+tDX0imRpdBoQNqxiDPMcqiNsMVhblqAFhkPsnVG+HafvixJU3w32 +FVxzm6KBX/ArCwJ3USgB2co7K2bzz1JRis+jIq/UPHM53L0FJjww9m+Nxij4FP6k +dQy/yM39waEybVvsV5JjClIqcB/LnwphH4bFcmvLSmHOjX4JDaYjyxv4HZkW+j9M +tU2HQU9PP2aj0cMAEQEAAbQjTWlraGFpbCBGZWRvdG92IDxub3NpazkwQGdtYWls +LmNvbT7OwM0EYs7yXAEMAJiP/bJ8OQBF3yMwZnX7kikCtOzwAYj7XWj0Wjy7cH8z +FJu5rkiUxpShrTbPmjuC3w2HMdLE/fv/WsPzL3fAaP729p9x8/RetuI2nP/SRRGK +GbNmwnhSaEW3L8KF9jS6yJp6H1XHvYhZwAigM7zB/FymHMcMmj7wXurIby1Sju4w +3+Q3AxErSq73eLxQSuAhCb8ly9A+4rQralvTcE1WPu+ndJYZvRRyrw61R5Icpxgn +XZqfGetlu+UQrZgIb++w4cEOpLgbbkl4DVn2dSOSTbwUw9v59KWCAxfLhkXX0DMh +G1Z30IcdbSxg8dPaSExTS6cjXLoi0W3Ci8FNLzT6qOxFejY1OCwvwzpDVGI8Jtsy +1b9Yj5zRNkY4RKBIolMqhIfUwdzAWEJR9By5p01xKCjBxIv8iAwK5zGkSQbG3vCn +NI/X7WbTTQLuvCjx9DpecBwpvJcskI5rxLyijSX33pgeRaUGsFaXNCAUg7fyMONF +gPODmk74k4R0fS49q/PjYQARAQABwsD8BBgBCgAmFiEE8pYjSYbjnGItMpGLptyk +Z7rjTIIFAmLO8lwCGwwFCQPCZwAACgkQptykZ7rjTIJz+AwAotVN9A5t/Mzwd5P7 +nGeBL72jYJTRCfTxAdes3BcZDm102+0cnqAWinOO0VubybxoKNkO/1OmLel1Qe3b +j3Cdm0QqJQL9luiIeRUw4hU94AxoTIvwPChQzdr3WAAdGieZASmo2zhIdvFkXarU +7krB1hamGT8YSBtXJiinolop8HFhED3y/YgdtOG8nQqyJ2z8bpvgUz8xQxyHZVin +ZFda/rCtKEjUXQ/I6uWLnBBKqinXHHmrSKvPSK2x3wvoNx5BxpjoFeyxf4DladJG +axTmGr/YZEHSbze+8x3YPP5dE1EZXlMNknqnSKx+60CccUdsU75UkrCxxTGLWK3Z +CLIoGpHaf22H1+sfQPg8R5oyAOuSapYACZqz6tcOwV/N5iZTKOosHEW5Kn7VPq+6 ++wMDx0nff0vX+MH5RuZBzc9jzR7iwX8la6Ay8NhSg3TAickYpkx/TOy82SwCs3a2 +Cb228Jj3dafZknlul8PyE6glw0uNPm+73v4q5moMRHWsoRvb +=QUnA +-----END PGP PUBLIC KEY BLOCK----- + +pub A6EA2E2BF22E0543 +uid Tobias Warneke (for development purposes) + +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBFJQhigBDADpuhND/VUQwJT0nnJxfjAIur59hyaZZ3Ph/KIgmCneyq7lzYO6 +xa1ucH8mqNBVNLLBhs4CjihBddU/ZKTX3WnZyhQKQMZr3Tg+TCNFmAR4/hnZ3NjZ +N5N5gUj/dqVI2rIvypIuxUApl88BYMsxYpn2+8FKeMd8oBJLqFRJ3WNjB4Op2tRO +XRWoxs1ypubS/IV1zkphHHpi6VSABlTyTWu4kXEj/1/GpsdtHRa9kvdWw7yKQbnM +XuwOxtzZFJcyu0P2jYVfHHvxcjxuklc9edmCGdNxgKIoo0LXZOeFIi6OWtwzD0pn +O6ovJ+PL9QscMdnQlPwsiCwjNUNue20GBv3aUIYc+Z8Gq0SqSan5V0IiKRHMJkzd +FAhnpkSFBvHhPJn07BCcb1kctqL+xnLxIdi7arq3WNA/6bJjsojc/x3FdIvORIeP +sqejhtL8mCBvbMAMHSBrFxclMp+HSz2ouHEEPIQam0KeN8t1yEqIy3/aYKMzHj9c +C3s8XOaBCbJbKpMAEQEAAbQ9VG9iaWFzIFdhcm5la2UgKGZvciBkZXZlbG9wbWVu +dCBwdXJwb3NlcykgPHQud2FybmVrZUBnbXgubmV0Pg== +=A+ij +-----END PGP PUBLIC KEY BLOCK----- + +pub A9052B1B6D92E560 +sub A74B04E5C92DDE1E +sub C03EF1D7D692BCFF +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=n6Mz +-----END PGP PUBLIC KEY BLOCK----- + +pub A9321EDAA5CB3202 +uid Werner Randelshofer + +sub D36DB5C489BAAC5B +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBGBoC2ABDACyCWLqqAo9NeThE90hBoYomtgLci5I8+7PxSYeQfzUYjXzZcnh +6d/zHaeC0zxGhT2LNe5i3p2e36xSeFDobjG2Il/nv+4jFCgbn3TZ2hEingPuPsg5 +isodOXHBSY9iHPTKInpiu+J79MWP9GVZ6GeOOgQaNSrNN3uDE1w3rukB2SrXGdfT +/6YZIOtwWZVzc2m9lalPmOAyI4KLI/s37C8Ozfv3c+Bx11WRVzWVpBTo94fU4DPu +Yzkx2Wxuc4TT5k6bQJI/GHi36M7xkUauXhkRCNvBz+LFVcFjtsv6S37L6RZib6mz +jZTW+iyD4h66gqzg3hSRxv4YzHgR/Os26Br2ioZNzH6hJFtNktR4oN/2kydmj5Od +WYjvdMmd/dMbZheZE3f8sziUKCyvWMIddnHAM81uDTXgWcMgYSa3npOGZhfk41z+ +w9o2f1kXIfdurqlA1n8np07PLw5Rfpjvbs7EZxPTJwcc1enaNyL64fb+YIUtFrcW +Dt1zUMEDkbcZL1UAEQEAAbQ0V2VybmVyIFJhbmRlbHNob2ZlciA8d2VybmVyLnJh +bmRlbHNob2ZlckBibHVld2luLmNoPs7AzQRgaAtgAQwA9dZFGqc0/6pUKDUsT+2h +Mh0z5mISLc+x6v4wbAqiEcBE9uoKf0LniRfVeehmuJpiUPgOf3TyxNbxeWiFuJEL +ZbnrYztAeRE66Zxk6i2Mv+207fdlj3mhphdEWh4f/0AJa2PAmiwlcxq1O3VJ/Wfn +3huCqIU5cmqnmGKH9LPaW1aJBxWl9+x6mmPVySxpTaQRrnNnumZf9K5lwGEsccvp +kLP7J1Nlv7HLO8oliPCInEMcsrVqfiKU/bybxNW1wzPJxp8LNLaCNxuLNl4RCp7t +PQuZgjcoCnz+JwcxnmNGI+FKqSsFXBZyNTWiQJuaFUQa0QON/+Qj3p+OyW15f+Ff +LjHI/BkNAi4ncNeeazAwuLCMJGMS+dLarHMbbAKLaH0UI062ch3Aq2Sa3io1gU6v +YHvoKNMNfYdNwoGX85EkzX6A9fm9jBviios1RLkpfO70MoxpWjl/rwHG57GOItWZ +7O53ebs6HrFPdUYQs0Gz/lj+BaQPwDcMxADfYtGnYMepABEBAAHCwPwEGAEIACYW +IQRurXUrPis46OIjbXupMh7apcsyAgUCYGgLYAIbDAUJA8JnAAAKCRCpMh7apcsy +Aj39DACMEaGsskfZBBbF9BHklmN9OHbGKK4qfwY6f3Pl+QEC4m2y5Uaxgx4n0SJZ +/p4YdVopRj07cdVYEkqJC31co0XPvfbjyBfhcH1BXkKQiYAZMUTqc+ILGHrY/vzP +27Je+vck6M+ntQ/2rBBiPtD6Bj2RcuXupr6MiGavF5U5vzLUkYm49Oq2Wi5gIuBF +8+6ipjnzrVl4HqGUlFEt8RK3W4I/ZWTJBlwxaIWoAIfkE54FtSqN4dY+LoNllMKb +zjFBemXfZXHAFEH9cjNh5EvFKm53ff8X+WNgodiVaH+qv2VMZZngEtmj2Y94v5x5 +z+Q3vcoGH6FUdLjefYm5uwEhhBsTyz8gyBmoj8cBBnw3DZexyOPLGDj/3FSimww0 +HXf8RIR7Msc5GGMLXqSZ4WVZM59pFsqjmqfgl+C8WYWMLAt7sXJGuyl2LQoAtR5g +pkPZqA0wQiIXe7/RgIs6TpqdS4uLh52KzW7VUXqywr+qH53RFujkS0KijW96Lbbl +ENk54Kk= +=DPTV +-----END PGP PUBLIC KEY BLOCK----- + +pub AADF2C18DCF95764 +uid Steve Springett + +sub F341381ACCCFC192 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBFkQreQBCADLaySdCz86fxlMj53KSYkZTRhZnRr6dhRLFVrVRuIW4JLW2tqu +/pkwCNYkT1hvUyEzuoCy166wKzAyucocyCIeOj2GAmCt/oH2IVvvBvouQGyCk/91 +oo87bu8WXdInz7oYnlq37ZOpdb4NJFkjgqYq63dUWtsuf4LQ8Zeq/SEXhFq/WCHq +eR1ZpNp21aF1uriGreq+bhtSzlnDkz5BNz1LYi7ho9g5/ylMe2x5JsDu8XRuvE0A +Yb9S+vtMzHMLK05l2bXnuJhZWjVm/d47UGEk+Its/ibC/EPe7I5w8msYSC3q/kp3 +T9rxP8Q/GDXmH75iwO/B1YhDrUppW0BbzUAZABEBAAG0JFN0ZXZlIFNwcmluZ2V0 +dCA8c3RldmVAc3ByaW5nZXR0LnVzPs7ATQRZEK3kAQgAt5H+cRVU9/v7NsJazjkB +SFRdAquHpWm0c5NlH8QeDlhIfwt1+5TFoG7kJr5f92XXiwP5eu0GHdpQUblV5/XC +aRlo4MKegOoQFtQ9GKoXfC4iy2PIDAPLC0TJJYYKZMHGZg0QoVyTQ8E9SqCzrw3t +EiPe7Lj24fDwYeja+uBMp96TWrR8RX1eitvZd4i+yRrD+xxSnzSKboyBBGa3fIbO +B/TPnbM54eFTKC7bLDXm7xTPUUTL62WbBjNT97iBHreRAmNVZIGtEQ8VcFxHPLN1 +yClhzod1ipVd85t9EndFe5QZzUzO9AWCfIF2uKf8lT7gTfwgm9F3LL5yQZ7sPS8f +FQARAQABwsBlBBgBCAAPBQJZEK3kAhsMBQkJZgGAAAoJEKrfLBjc+VdkXPEH/12X +UVrBI+7qiUupZiun6r/yt/TPGFb+vKc+mBxL5cYKcbL2HQDBydNMVCCl+wWdGfa4 +xpmZbmEYVJRONnZzMcv6yU5Flg4B9KQ6xjUszLKP0GISyLDWJOvlvLbN+vvlhMfD +vLMZUXD7/JC8gN+VOafdVtWn4TVMPRGRRoUcAdz919CD0oDl1tZYvs9/E1jVRROO +1n0SLHT/HmqF+CMleIqvVoTt1/33SmI4OfdyI/u5bcJ/MpPjM33dDC4SIwxUq0V+ +oLKdXMRbNxg4SY7Pt4nbp70Avxh2bcFBja09WsYuEZn+6p3BRmcny0px92qhmKNd +zup8Hq6LKDqoaTcf3Qs= +=tBOh +-----END PGP PUBLIC KEY BLOCK----- + +pub AC1C3AC303F1D48D +uid Petr Pucil + +sub 6D06048E13E7F00A +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBGHYHasBDADmVoRYrHHgb0e/gxrdghwKH0gewOTV2r3NfnfUOPd3nPzHh90F +zL4FDsqhVeYCj4Fwc+ouN3XKUQcbjC7KbqfW7t5oW9Rt7d7OROCB9qqLQD7UhLUz +Gdny9lTf/TykMIRQVoeDTB/22xzPoZoFXKGJYLE+te26yTAPYWUO1nUv6U8auG7V +GCWJ1KPXtDu1K2Vr5S+UkHOqfpchghqu+Q4+Dagfv72zfzIAXYTkEyZhmYSzJ/58 +96oLao07D9iFbHatPvoJlPl5gIR+CrJPDlWkM+x1T/aYcBhAaP1AfUFvobEanSFS +GBRpRfBwULuMGg1j4DrWheencqhaoVvZJPL7QB7Uyvi0mTBzsO6KGQOJLpuGMqZA +f5DfjObkqhhdPDju3Y1dkpEqJfnR6nhb9eKa0oyghgVvpRq7p442Xk6tdiqHVZCf +I/T+zNgMhzcC6vDBuW05outg4n1hVd6lW98MWeJBr46qhOkc6f+fTmpE7AJz+kx0 +WqwyQWl+N/11SFkAEQEAAbQhUGV0ciBQdWNpbCA8cGV0ci5wdWNpbEBzZXpuYW0u +Y3o+zsDNBGHYHawBDADJJTNH93kUSX23CaWA7JORvwds3ze4B05BZN5oBg37RcLg +1NIWQ7Wk474fQLhyCLErL5lSm6o4I5fIIP/P9aQTy/FZhBsug/zxUHv4XOiCvf+x +OMrTNmRtn+ERBIpWUKGtQxPFQbg/3VZQ9JP8anriLJg1j0XJ77EzZcsmHAc8RjDE +a1Xnal4Mvc2f3thSSqR2nKNbvb7/cDNuVfP8HqWXSJ4K3nKoa11U3aDjizy9Wik1 +CwW7AqNjAYPbzWSpUbKDQ37u4As3IgZYH+MaiFEk/fP5XSgil5xdNRc8sJQL1eSY +bohJgHGgJXQ7TJztOKJwnCjvfzBRiaAwEAePrTUzArItsEUi+qq0Gnze1QLbg+oh +8ctDA9uH39U9QMDktIDGIWvaXo/nKgdkqfZs8mtno8Vo9VfNOJ5257HzlLvaBQDB ++SO4I8dKN1Jh/hsqS5PO3jsAfaAx8AWrCcob78zlKXFx7E3dtE+QxLiUru6SnXdP +H0oeXoN9ElpUOTr7eCUAEQEAAcLA/AQYAQgAJgIbDBYhBE4Gj0kFMlctCVgqVKwc +OsMD8dSNBQJo8CV/BQkI+TtTAAoJEKwcOsMD8dSNvPYL/1nm8SOORYhRUr1PIw/j +82KI8ayYiM690+msmLB92zpGXDgkngKT0mvUj8okWCaYlSrQIOOT9Leva63HfGlU +IsblFdQOITPk8bIpxgdqFS9pqXRfqn/vWYiN4tm2HnrtRLKXj5FApW8NsInPxSKo +D/0t1CLMqaC6O5IfzE4kzRiOvAVxYXoDuPk6EvIyOKwrGdof7rcTWs/YIXZ+wqV+ +FyzNn6CWOzAThCA1x4+csthAGfNcj7PNbujcKnGM542O/yXDtyfcrJMsqlTX1Y+I +95jJbVUn7efXO8NOyueQlvJM6xlJn4kmd1SuULZPkWRvAX/YC/Vtea/5f2QcGUsl +u/r/1IwMU+ria89Wn+r/0ykvNIEq0jIYDiEJjY/Tu9GFsOR8gmyh+m8wvJmDHNfi +d0FiUm2vd9P8kmkjnKjT2M+yayIR862iRnUeglIo5KYz0fGh9SHD0t2JHE+GoKDI +oZUDegQ8tfOgq05hagbhGU/bIk+yoOUQu6J03D/KUQiGDcLA/AQYAQgAJhYhBE4G +j0kFMlctCVgqVKwcOsMD8dSNBQJh2B2tBQkFo5qAAhsMAAoJEKwcOsMD8dSNAAYL +/Rl4Ra6+4mWFJxBqmaSDwWFXN2HSGm+dXVERCTdMKDt/oh9sKPOYEiDmlwqdSmuE +RX6dTC6UsW0N3blMFG5bjy86+6gPAYn24eRGE4i1K0e9HLzRxHJLazMcfO+j0lh6 +bWoongi6Q9PZMWdJPRMeeAAm9+E3drF72PtAOV/3hdKKqMqaF1T7VrnUMJRGvRTl +Rb57TBhYyDMM1x4MYLWVRu/TSx23/b95CCUCYHMH+5/jb+6oZp3+yBSqTmNkxaNS ++WhEuXIpwc5dY0v9fDWkYTvUpT+S7MpG8jE6tgXfQIKmrvpKUwVRpY6PdxsauGqJ +kM0nD6sGASdRUwn/bvqKiHvDek0tqqz2dm61azsMGnWlmREcg4EOil3dc0uSu6mO +oqHvg8bnHuFyXOR9D3gMfKVwj3hz4DFo88ZOfzcqA43/vC/+XqjPsmrlGEZlfotZ +Y6oZzxjfHw3h+ku5tIldunRaSCdZPeIzCTRgz5Mhn0W+5L369/sDxkNzU+SFrWDS +qw== +=87gb +-----END PGP PUBLIC KEY BLOCK----- + +pub B0F3710FA64900E7 +uid �amonn McManus + +sub 7892707E9657EBD4 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBFdbSfIBCACrFI0ai/abnV2U2Wa9QQZwGk3Fegc8laiuTKc0GoYdyptd83/H +hD5S61ppdkOugBjVTHdgda3xJ7zBZdnwjZvV/TyayQltbh6hU+BMlEolzXLgyvY7 +cAzKE+iKWbLLwfhRn1iuC7s5l1NLPsh44IUt3xDaFXNQrPO5OnRz8bqsGFVawxmu +2bPqIjkhxEiYpxwaZZbDkgBR6rbBth6A7QOadQcj/9wNdekoM9dyg+olOUmnLrtA +nMBhrvvbm2fZxTps3SZHlLV7+iSu71B5SqU/kT54/49n8vxrQiGvzp9K+t7c7EP2 +w4Ax1nYpRkCxYdHOX3YBdayUiP9ZaYH/YHtLABEBAAG0Je+/vWFtb25uIE1jTWFu +dXMgPGVhbW9ubkBtY21hbnVzLm5ldD7OwE0EV1tJ8gEIAJVavNan4WxxlwLwvnBj +3/wcEWqN+kfMHENMSjmRWOYSmC332hhGLmTDi++BPWt2OOvHUusJV8dZP5D9yUBR +FsKozIpyXyS76C5VYGMY8WZ6kyqn/mLCiwmnkOJ24kXLaaHPsQjv6i5f2KliDVhA +GUHmNMJgH8o/GL7zZ03Mb8ZlKFZobp0dn+/lxoOtQSzR+cBz8NvMBkOKD8r4PJA6 +BxCR1HVEHsq4xSnjr/UZOYvh+Kaxfnop7Rn9in5MoY2rCY+PV59Xbx4grqNpjupy +HEf1MHodJRj85JiClnLZk7dNJ/kr+zggwbsd12/GHkBt/pxuWhe0eFcAOJmvqC3c +4pUAEQEAAcLAXwQYAQoACQUCV1tJ8gIbDAAKCRCw83EPpkkA54FACACFZB2Tk96F +Qkr8+WHOz93CJs4UD88PosLaKmiXKP68arjH3y5jhNLBzqteZo0Crfw75DYWIZCh +df5uLGKCWXBEytF4uoHOy9Lv/3emoSeenluFVcNjL7CIOQDRmqw1t/LjnsLbgvlw +Hix5f7I6Txu/J0HKJbq0XpoTqCzFK6sxEPHH3gZto+XfHk85haKd73SOM4edkmJx ++jDXES1wb3K3SpYibt+uPVfLYXWxK7xAaztESTIqZ9RnYHzd/7z6DO4z//lfB7IV +AqvM8ga7Qj58ObeqZxx2iVit5WUZ4cE30crNGyXH/HKlAp+B9EvENnwr++TI1CGY +thPLFHFc831L +=s4u9 +-----END PGP PUBLIC KEY BLOCK----- + +pub B16698A4ADF4D638 +uid Checker Framework (Official Release) + +sub 32784D4F004B405B +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBFM1v9ABCADD0KoXq2ZKlUHeIVovQy3gFmW9oFAaraV48ouv8cYvqdf+s91H +NyqeyNPT/ihFeNqZJUAMyPdwN5xrWD6gxMrOCR7BFhA5kLmAKz4HfFCQ05ViyQdI +/HVNFvTdF8LNnuF+a5aNgg+jjLvFwzkyMFkuiPGuUDFnqEGxC+z9J8t40tpOTOIw +tPjSzkDN41AJDpUK/simKC5F0Im78nUbwMalE5z2IsZRWpYZyIhN1HhEdDvaDIh7 +3vENjH7enAjWh0iGRu+GTP/fayZnX0uhmausCCwMMhsr489e63ZOaJrqeC//wWrX +dtEJjcmvRmJ2hwLmgwMP4zSNKsnLGzP0sh69ABEBAAG0TUNoZWNrZXIgRnJhbWV3 +b3JrIChPZmZpY2lhbCBSZWxlYXNlKSA8Y2hlY2tlci1mcmFtZXdvcmstZGV2QGdv +b2dsZWdyb3Vwcy5jb20+zsBNBFM1v9ABCADutkjG6oCMxBUBB5cTTeaWR3e5rKgx +EiCxWBZCNZsZZA9LcBVjG5OJzB9lV4Yrk97paigTlFFDUKzu3oLX2xrIFb+G1m1B +33mZH76Fg5Zm674tWC5Uf2ccxqQjXPHt2jnDd1yh5QcH1GnKOqXEwby6SjwP0wI5 +EzrSuAOQM79QksKc0iX9m1VW65+5ov68O/EpmQFdv67YjlOWvUvt387MC5NTzv8/ +/3eFaAnC9rNlrnlTtUPfZHo5BOeZd5WMBIgc1bgAPfENGucIPOL0RhWUFiyMPHNt +Dp9vnWXEy2XOtWY57CNS0py1FMkP38x0Pgcp0BfZeN2QjyhSJdduTBopABEBAAHC +wF8EGAECAAkFAlM1v9ACGwwACgkQsWaYpK301jiXpQf/bw3Nxv5qyBwdT/85dBXZ +ecEM2klXPSQf3HtNNfKbaZS+9dWn9GQ71qpmZCTZGLtJR4J54mlwdJdxhlDyGv02 +c1YBUkT4+uRVkzJAWzZ4RiMFeSFTj3Eiksg2J/f50D6ZlpeXw4/MYr+pCmMQOIY4 +0W0RrlF4iNnZ8hR7haWnH/wH/zHNFPwgw7s+WtY0uEmLmDPxxVS/dzzmc8C1Ef/h +g7lSRZ5tdq5oxpyVYEdK1nCSiberwrAT1XyGpn5erxvjeE1kPXro/EXeIY7GDzsA +34FSFBiIaU1Cfn89OOn5M/TFp1+0SYeoaiwF0+x23NBUxbCmAGyyW5t0Pq1PY03T +PA== +=Vx1B +-----END PGP PUBLIC KEY BLOCK----- + +pub B6D3AB9BCC641282 +sub 700E4F39BC05364B +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +2bX9MtjSsKhYeVDZhwUHbX5VuRtS+X0IIFhu1Q== +=xPkk +-----END PGP PUBLIC KEY BLOCK----- + +pub BAC30622339994C4 +uid Chris Povirk + +sub FC9BDC25FB378008 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBFlMExYBCACmdTDSXPwSJeYbfYvHoDl5C7vx/0+LOTunDGJN38pNQHYQAZnv +Gyoc9ZmChrhLoim7z4ILqmNo8eegknepQ3dGdUij4NVIhR+m+8irayTbsNHvo3UG +9y7eM5tTSjyNYkyk5fAVuT7OhzIzMA+qtc3GRVxNYRKnaHajt+pOSqr+uoDtMG3n +6eAMHCAnhgh5Nd+dCFcNT+syl3zCwolA1wrzGxxOaif+xi5wwXjmF/lAt4PDIuDT +etA2/AqPM4zAC0BtC0iqVgVypjFV3EAexm/g0LNMiG/M/krzwjPq5gf1DY/57jU0 +02FpKd79HmR7bHdc4e2olEf9NlHxfbPXDDsHABEBAAG0IUNocmlzIFBvdmlyayA8 +Y3Bvdmlya0Bnb29nbGUuY29tPs7ATQRZTBMWAQgA2YylXc0rxousuBBPKHn75163 +wLcPdPm2QR2WMc643fX+p0o2B7wfq4ofzNdFmyPml3xp2laQPhPMgy/MjDOLOwbD +fg72F/UTZc+gOq9Cvf2nWyi9fc94O9Elj/skU230OymFVzfFZB82QLsl0w9rolGI +Ud3fKd9tD7CmV/i19oNWfUlcBanQ1IaAy1d+fmLX2EdBaOZE0Ao1At1WfxRzMjxd +1UzttLiCIOlAy6RLKOyOACjjrqJErb7x/wHAhF7UlG8XfsF2du9OD93OiLX8Ti6T +BUkPpgTYuUPDN0aej0rE+VGy5jeeeF5WCNusYnZcpEmQiTztwwaHjVUrDovi6wAR +AQABwsBfBBgBAgAJBQJZTBMWAhsMAAoJELrDBiIzmZTESIcIAJBWIY4eLQGhnvxk +nPovjxg0zHx9J1K/dgnzciHWfkNYImCsNXlLf951iKeShUjQu6zLcx1+1GDo5Zpg +645N/oxhx107+ZjfreOIwHJ/1SHcOqriwqpUtGdlsCqAPWh1GiPY2QRB+AQf8fgl +FWvGq7MAqhbAmHcyFj6RqNglEnJZ9zX8GbcGPW1wwaUZ4hB+BaF54yDeTGvEn1Nc ++pWLpVU9o8ohTmLyXjpL5aDe68yzy8LdfM4i+FjpzCC4m/1SSNnAYNIj3nbAuqdE +QzLSAVn44AHSomlgzecMR6RZk/fwbzSSiOc3rW1mNFOG70iLz9v4/BJb6dbcSlBl +0upm05Q= +=91T6 +-----END PGP PUBLIC KEY BLOCK----- + +pub BCF4173966770193 +uid IntelliJ IDEA Sign Key + +sub C9F04E6E2DC4F7F8 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBFKneXIBCACtnX3ZQmPujf6ocvdnhsBheze71DSl34TfebyW2Qt+g9NhMxo4 +DaJy+iFNnsaMwLZRr6k/qf+ISE3A4opWAQlbk+Wb5s6DPPA2cHH6W4GdkxtuJzqt +tFn6YtkFhA15Aahr/vz31NBjUJlBmO4PwvkyxiF/MYP6TQ/AHar4xP1RxSYEPcCi +dIQczQ8nXzya4OqOyTfibeGz/eiHHuwTLHi3Rd2kihQnlRQdhE1rmm8uTyzFe1H+ +P7WW7kQgygW6yxQ3J+DXrG8kG+nbe57ZY1oyv3F/fOBxzn/kuoKHZ3JJEMJmTIrT +Lr1ngCZApgteAynRHk4t/SYZiyoyqZCuBcwHABEBAAG0RUludGVsbGlKIElERUEg +U2lnbiBLZXkgPGludGVsbGlqLWlkZWEtc2lnbi1rZXktbm9yZXBseUBqZXRicmFp +bnMuY29tPs7ATQRSp3lyAQgAvc8Q7O0gVSJsHoVgSQ5tWGwNsKcfD3I7kwC8BYHr +Q6F/UnhP1ArreNnn8KKpwOvD65pv0j5G7P9KAbIVLRRcCTB9MgJR2FPmRTNmYbKi +Pa6X6IUM/25R0SBKDJddqSvEFsE/M1ozHz4bIhdFUXJFMfv7WBaA9Cx03WwZg6Bn +5/xMzMC/qzG7QlXOMpcABtd2JlPImH13qHWNLkhyKW7y9HCfdBz9nOy0FGT54ttv +r3BL1gahSXNi8MHP7m2I3C8dSuIpzrNVPgR2eByvSYpZN28P4Cy9l99TRcr6/FuA +y5FaL/nWpv5WAraAV4Cx5Xpr4PXTn27b7k+feH8W/+9EAQARAQABwsBlBBgBAgAP +BQJSp3lyAhsMBQkSzAMAAAoJELz0FzlmdwGTSqAIAJ0/yTJRlWp+dwDZGxAffw0V +iEHPkwAQ4iEKburA8LpcbTwJRl+k9d1RvFkZSWITq+F+Putlu9QooeVwcM+ht1Mm +oah/aO3Yx+yMnXwljR7FJa5VOY2aoALeCyIx8QYiqNAVaid+bQ53gC924u5zRM+T +J+vSChtqSBi+EOOTt5C+ALVB8qWTqEcD84AVbvvippCzKsA2sV69FrsIFAShvpXo +3xpXW83GCXxrp8nM9M0E46Y/SarvGTqfKRC6phNUXKp9c3SnVttPEcGhb9+92LOL +vMxKy4GRZS18bXDI3vS6gRDNJDCqBYIhp13Os9k+ZpnwK3PPIHv4l1I0i0EHZKk= +=E1B4 +-----END PGP PUBLIC KEY BLOCK----- + +pub BF1518E0160788A2 +uid Karl Heinz Marbaise (ASF Key) + +sub C163B490C5CDC967 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +v7q0QO7B +=9OLO +-----END PGP PUBLIC KEY BLOCK----- + +pub C92C5FEC70161C62 +sub 64863FF4D1BF1809 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDiBEdUhrIRBADCU9cuKc92CWQlZxwtRuSIV/36Qmj264YD+Lix+r1Qe1PqRr1I +/MObOo83ulorWigSkx1k81Mnr56NwmIeo2bMhjmgRgf7EG6XEbKdRKfJcJRR1lDV +Ml4ru40W958M3PX5fsi0m0g2TuVrAKIS4vscUt4L/Cf4IT2/0OhaT6bWswCgsNws +Qq6NtCkLkpWSBNYGT4zb6yUEALlhHMnfzPSDerKjDOaYHTz3PRc/GGUDSBTSVj6W +hQIDrgTqrPxoB5JMnfUz8BLSayk0d6HiwspJ4Wnxe2/jdIT+6xhX9xBYXVHZVs4R +cr8zbBNcW2kwFg5Mqy7TiAPzakzCslKAAX+cjAKSOWyRbmkEYnNgMlctdyENOR9+ +BpP+A/9anoVEfULqoETShmgWdi94gx713qymhNBpFZnPpm4j4JuxKopl/unQmw5i +Jwtu93cg38UfaOMJjTi6tJ6F6SE8xXv43nKs3Xb+Ll1MpTgaGUXEhCOeTZl223Qe +NBUp8kvfcys6aVX6GT93dmWxtMewlc6gc7HVQnUnyCFsVeoy/87BTQRHVIayEAgA +/OzW6erYExaWTjI7wPnD1uv1Leq2WRhG1I5YfuKU7K91TMilBm8L+qCmF1QEg7yF +6mYtdwXjOiA0YoGOVEeNJELhJFKZOoeZob+R3DC05uUsBl7xi3NgB8Msags5N4q+ +nqZSMZaEDl5JR4ZAhYCZBy5xBmnvmRPUL50CDN2IBKxHVOaUllBIZDtdtVH37Gwa +VzXuhPxsLiAOeJ29W1t8RrIP9TjQlPhzwu7P9Fq3/JcCmhF8xOmcn3wfCZ/VMteF +Vp8aTr4aO4uo0O/HYisbStUw2YDGe/RmXiNOD8CXHFOg0/c4tettRhtnl8OO3hQ4 +srY5eymBG4tnV02l3/Y2CwADBggA2i5UGKqWDJ46LviS1rNzBLLHPv7GASFicQY4 +HxMTvREdBIdb+p287azp0l0ixaLQOq6HgSMZbexRG/DdDSakxlOr+kil5NJnHmZ9 +tXzGmnLP1WoxQEc2FVdG/jKTg6gz2x9Cz1pRMxxAHN6Os+c7hxYKbD649fBbGPgZ +PP0OCjwrHVfu3WaMaek41QxnFfk5s+YNENly+XfeX2PuYLwKwuVkYJftqohU9bRx +0phdDgQWbIZMVzihxr5yTxfkCvmHlrLHn+lAOz3N3xh0Qh/DofWEDAee8uk+pbzC +XSON2v4iO9lsHg+wXYLREBHxdE0EreZu2VzBFa9iN2nhtJnuTMJgBBgRAgAJBQJH +VIayAhsMACEJEMksX+xwFhxiFiEEGQ1alX/yInPmAfenySxf7HAWHGJygwCfTUtT +D6aQF9AkWwwt0KnH445Fg84An1hG98Cj9efdOoxMt6lAEHX1eHLq +=swvD +-----END PGP PUBLIC KEY BLOCK----- + +pub C9FBAA83A8753994 +sub AFF3E378166B1F0F +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBFeWvEwBCAC7oSQ7XqcGDc6YL4KAGvDVZYigcJmv0y5hWT4wv9ABP4Jhzr1H +NDmmGyWzhzTeMxwuZnc9vhxCQRwyxj3gGI5lYPEARswbi2fWk//78/3Wk+YMHJw3 +/1EO3VqvvDUt39gbaSqMCQNHctnFdb2QYZ7nRFTQeCqG/wyMdB05beqEnWEXzjeP +FDF9y6gXkELn0lxUm2TKO8tU3h96TCuutDKJ0aE00lOeh/MbEaGHEbIU8kdfui6U +znZ1X80EWbkCY8cKxEZHKD0aONSVHXwE6nETvFW9/9+K+sj/I7ytlyxwHsaQpi1H +6aRGnq013VsIECrwkhmXBsLLXNjmhER+LkcDABEBAAHOwE0EV5a8TAEIAN9uOpE3 +Ua9J/1WSMMNYGpfeEguI/HcMo+JIWZKwCiItISQ/yBEMEPLqmj857P2r5uBv1KT6 +IaJ8m9tU1mvv7zwtLFAQKytUv5mBMBnYuSoAFAnxdiH91M7oEwnmtIsf9g3ps71X +g2Nih3rtbm5ijH5oKnqR4TuJrt4EdyTbDKrGKQKq9XOYB248KSQ1JG47AuQ6C525 +d/BvsKDVGdpwwwR8N3235rrK1j/wkW7TUb75VXEUc7e+z/9Eg2ubQ7jEo+RPX45x +3j6HcOWGFG9Fe8j4wp4zS53Q6lRUIEoJmpsUpNWChGmwoL3bllFRKpubIFwiSrJi +PMPVp1pl2Srg8sUAEQEAAcLAfAQYAQIADwUCV5a8TAIbDAUJB4TOAAAhCRDJ+6qD +qHU5lBYhBGIUdgCX3Fz60Bdawsn7qoOodTmUOrMH/1ZtJ3QXL3StKgqLm0f1jrMp +0tcHUNqxiiQuaFbFDeGFQmYYPTjIcDEjtxDgT3cbauAPG0maf/GVphy6IRPEBw/A +IGkAbUWcjZLzEYjdee1xpDxAUVnR8OlwL8f5RN9VvtfahUZwBPAWxERN4IniXBuA +ilsuQss1540jPs52bw0PCezHxvi8Sm6+81B0B/WVrJPFfQ/hlw4KbsmXOHLdbTQy +3J+u/OBbm3Haw90SzIjgGEkoCkoKBC0cwfM2XbPlihbogGF2Uncwm4ySdlapyZ0L +WBze2ea98kqmxu8N60Xp/hLbej1/R673NTE8v1FHW97NPAtMA9Mfmcxc6lFyk2Y= +=RV68 +-----END PGP PUBLIC KEY BLOCK----- + +pub CA80D1F0EB6CA4BA +sub 5EAB8AD72016DF52 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=nwzN +-----END PGP PUBLIC KEY BLOCK----- + +pub CB4932073CBD2241 +uid Kenny Root (Code Publishing) + +sub B4D6B92E9CABB6C3 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=UHh6 +-----END PGP PUBLIC KEY BLOCK----- + +pub CE8B1D1D2530EDC5 +uid Eclipse Implementation of JAXB Project + +sub 7ECBD740FF06AEB5 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +Zo/Xr9x/cYkFS8ieh6XR0y1Yx4xZIDTi3Q== +=xz0V +-----END PGP PUBLIC KEY BLOCK----- + +pub CF9F3090CE4CB752 +uid abego Software GmbH, Germany (Key for Maven) + +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBE7E4m4BCADCkqre+MJRRn+yBa8PqDHFIpfxOk8lQeueZTrU0Hw14wMkkOW6 +XFBb4hDeezStNNP6s2TS7bf5YRXZwqOwwgg33WYVVH4jPldaP1m+Z3GtYSLKEjTl +G7/YqLcCtLxDdhLF3WpR7LUyZFQpIPEwRj12UyK8gU/Wy67GP3JBz/YhGTGfEaXE +13VWM6FLvS6GJVouT4lFVqPTVv54+RKz0GdlgCB7Ht7kPtpMkz8ceKGU8+JcbAsL +zkcicA97ZBzYEnlHFfoNI4M6EZumw8TXM+hvJBQ3SEWenhmUj5dwkympTahH+Dw/ +iam4sbZPiQH01GLiffeNNfTVlf0bvaczvvG5ABEBAAG0QGFiZWdvIFNvZnR3YXJl +IEdtYkgsIEdlcm1hbnkgKEtleSBmb3IgTWF2ZW4pIDxjb250YWN0QGFiZWdvLm9y +Zz4= +=lede +-----END PGP PUBLIC KEY BLOCK----- + +pub D364ABAA39A47320 +uid Liam Miller-Cushon (Error Prone releases) + +sub 3F606403DCA455C8 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +xquhnpl2HvE= +=a8zq +-----END PGP PUBLIC KEY BLOCK----- + +pub E3822B59020A349D +sub 9351716690874F25 +sub 60EB70DDAAC2EC21 +sub 3D5839A2262CBBFB +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +7NiDKVeUGNDvkoQ7y3HGMcay4JG1oQ== +=Jshu +-----END PGP PUBLIC KEY BLOCK----- + +pub E7D88DB8427DE651 +sub D557964963279019 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xjMEaFwfvhYJKwYBBAHaRw8BAQdAEsx4lY6imvhSy+Um2DbNEfH7oOJmws4i/oBy +iepGULTOOARoXB++EgorBgEEAZdVAQUBAQdA3bv22tzo93V7jbULXk6BHxf5rzi8 +bX0oEyiHHRfShBMDAQgHwn4EGBYKACYWIQRuf5EAF2VWIqiZY57n2I24Qn3mUQUC +aFwfvgIbDAUJBaOagAAKCRDn2I24Qn3mUYlGAP9My4h88h8XASO55IhMca7EpfZz +ZanyHDxGUmeaiSAzUwEAw9oqWpF+Gi+ymc7tayOZygabbDeneSgUe1lxCyGDHgM= +=sv5V +-----END PGP PUBLIC KEY BLOCK----- + +pub F42E87F9665015C9 +uid Jonathan Hedley + +sub 6064B04A9DC688E0 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDiBEtsF2oRBACcai1CJgjBfgteTh61OuTg4dxFwvLSxXy8uM1ouJw5sMx+OKR9 +Uq6pAZ1+NAUckUrha9J6qhQ+WQtaO5PI1Cz2f9rY+FBRx3O+jeTaCgGxM8mGUM5e +9lFqWQOAuCIWB1XPzoy5iTRDquD2q9NrgldpcwLX3EVtloIPKF7QLq72cwCgrb5X +R25dB8PUdZKUt2TtJbjB+SMD/1UzAPirgX0/RpL9wUR1i14yIrTfpFP/yM9PE4ij +qcZ1yafVdw64E1k5W4k+Pyl4D8DvSJvbJHvYjg8/G9V66WzaKcv+987fetUuePvY +/rwxBPztqq8y6+hjBc8QVhZGWmAoGGEFO6MIGsSyN5ohqPMpNXkczIo+NMvDxGzz +ld5ZA/9awGTsigBdpBK2F6GOmbvBv+Xebu9rbaJvBvP+npNx01s/f5sHPCxmBTFk +m1vtaMdZ29RovrWPSZRj8WWes0bcisw80250r1CBlYzGzqEVZ7b0Hh2RfkfaxbYh +wikyfTfA2iX8TUGBgirsZbyegjUadElhwFNDASnvLTEuQKeVLLQlSm9uYXRoYW4g +SGVkbGV5IDxqb25hdGhhbkBoZWRsZXkubmV0Ps7BTQRLbBdqEAgA0sZ0JZvWoKIG +b+o6MOwI6p3uMb+iWBwdYfoh2RPnUZdBwGhJjp32CiTt2Y3qYEcqC5NvF5FWdx1m +5KOQe1O+QFoqPKnC1bPj9uZOjLVql7x5tSwCePIaMNB+fMxEh5hYwLWtBz8nrdCP +gwm+nAwecoE8YfrpmrXZk/YLak54FOeEwLYaP8E4u2FHiEqN+WmKMjIRwLzVpYAr +WRCbTLhSSKyRBy7UxEovUH9mIa4YuU4Pb2R64LwopMHCBm5ow0U8kCw8vpW40GrB +c/2eaIeXCX2XJ77E9s9ZPgW6MoJ6Ic1xV6voLJKIEV8t44deKNSwDfVNZHxyemaK +a8/GgpjU5wADBQf/UzL5lXRmyTdJqRvHIfUV3g4A3X77d3vOroab8KKw4MFy2LiT +ioN7btKKxE97Jjp21YZFd7Kpmfu2i/kr9QVJo+DSxe2p2xcQozyS+layPK8h/61L +hyh8vjzV5AUWA5Zup+P7Jh/WRlh9Gxs0k0vimYMFKImw3mZr4EA8UCj2e85XIHNH +Bd0B1VIukq4OjU4QhRrutNebIy3GZ35ylcaXT5v18Rq/iRJAuJFoCzXUaE90/V9/ +2ob8A1CYEKGLocvOQgBsj7+2gP5WOP+WxI4TWPENRKMVchVBE8zV+7YZiahPCwOQ +r9TQWMaUIJxZ85yr7O8DhJOBX3B7EHIfpoADXcJgBBgRAgAJBQJLbBdqAhsMACEJ +EPQuh/lmUBXJFiEE8xhLzVX00BbjDUyb9C6H+WZQFcl+zwCcDKIILbGBUNHRGY57 +mmZ5xKMWbCsAnRbmM18GlK1TKRcOcqqEPWSusurHwmAEGBECAAkFAktsF2oCGwwA +IQkQ9C6H+WZQFckWIQTzGEvNVfTQFuMNTJv0Lof5ZlAVyX7PAJ9ztvyEP04cy6zP +9lHt0qXdrucDfgCgh1OIUk0pFzNYBt3PXvOeyD5FQbk= +=V+8I +-----END PGP PUBLIC KEY BLOCK----- + +pub F5C81DE10A0B8ECC +uid Andrey Somov (SnakeYAML) + +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBGY/odABDADzlZ1BXT0zN3rL+z4HP8r/2xM6zN950fwRimBTOiT6uE8aQSxq +283R/gIgM+yQBGjStLP3k/TsFJ2FCz7sug+7s1RP70ymkshalTRg+9QHBr2MU1Cx +Xoh3fiH8BLOb3FIxH1wdAtOOoNxbz64Ftcptu3L0I1y2qEwOGNOyvqbntdCuwNbX +/zwZUyb3tOVVrrZ5bp+6jMoBKEEWS7effqhGqXLlO4yTMBXR4pwzhch2IGCe+4M3 +a5C2SIJbR70PMk6aJ2+no2LycYRYJx/t2umAbxuCtwT6t/xh8v5ekbXAu5G3h5y+ +T2bF8rjMhVe6DBgJ08uFge3Oom5a5uZx8sQASdLCng8nKjGO4Q8jWmsEj+OiHnnX +g0oKkirnWbAVrWysgNKAXfwGfDBG95K1F67kVhNjXTx0MDcxpsT9TPxz/nDuzRpQ +ey5M11+Bl/fEM5UuzRpPgPd//bU+L3FgEUguB4kzsiYlhsUQRyCq1x868AchLWey +vaVIq2DY101GIP0AEQEAAbQxQW5kcmV5IFNvbW92IChTbmFrZVlBTUwpIDxwdWJs +aWMuc29tb3ZAZ21haWwuY29tPg== +=R651 +-----END PGP PUBLIC KEY BLOCK----- + +pub F6D4A1D411E9D1AE +uid Christopher Povirk + +sub B5CB27F94F97173B +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBE89LqsBCAC/C7QToaRF8eZgGOxcvp9aG+mFFCMjaRAb4Mh59OYdmUb6ZjfO +9388HPebGbPNR8SHYs0dBIuWY4ZJ7oUTYPswasL8vB0iPFdyHhvkCca+yk0b8ZBM +DmFlISm9HkYpoVjcFUp1oivyeJ5LRTJTd5JGEd/SWFRbB4TimdKXBzej9fIm2zVl +KInEMMd8HnSYE6nm3aNkbyiqhx81bFvl8x6X3ZMWcKs+TAVXdP9uLVvWowUwcApk +xpee442Ld1QfzMqdDnA6bGrp8LN8PZF9AXQ9Z6LTQL3p9PIq/6LPueQjpJWM+2j8 +BfhbW/F2kyHRwVNkjaa68A544shgxJcrxWzJABEBAAG0J0NocmlzdG9waGVyIFBv +dmlyayA8Y3Bvdmlya0Bnb29nbGUuY29tPs7ATQRPPS6rAQgAuYRnTE225fVwuw1T +POrQdXPAOLDkiq49bLfcxwRJe+RozKrJC1iKxb751jTozEEJLe5Xj7WcojqgDsuT +jzaLHDNvDCzRFvwfkJ4scMTAZd+2GYsC8N3Gg0JRgC2lU4wZxsanLnVMbdX2L0lZ +7WnH6S+GJ5f0Et8PM/g+V2Gj2UraBhGGak8OBQ6NhmCJBcyYg8Bh90cgD9V1hMRM +LSW7gB1vnpLM7C8Yymd3etdZSIltmDuVb3uG9s4Uwq51s2MEKsXsuFYCHTz0xT2u ++6e7Puaq5V0218QGR1Wupkl29iIUF57hFR7f6oYKkecvPKc4Yev6Ii0Mbvc1H19k +LOXUrwARAQABwsB2BBgBAgAJBQJPPS6rAhsMACEJEPbUodQR6dGuFiEEvbX6T+cZ +14f7PTGX9tSh1BHp0a6dJAf8D7j9luvaMHjqrUkQ39RXhTcwFCI28I5IP2048ycG +9XMnnce628YaSZp9u1vANlo35gyzp+KK0EyqMX95D+knnhoWC5M8YwWuUXKPPaf+ +l9+QculUeCzxXkzgAshO23AI6jxW/u7dWM755rmSIKb0yonJKtQ/YO/iU9UHfZ6g +RSpYPGjJ4AKKFb5S12jxMENV35HzDfpbcJRK+6NbbP2Mw1MX5WhVYNBZze6ns2pv +7O1b3CuOqzveckK/1ss9qFQ83N+Hvja/29qTdOTAxwNHV5m/4q8DwZdJkzoAIAvN +OapEdeMYXdRni+jBAN+JPNkqvzt4FoQWgdyjsuef5b7yqQ== +=Gw/x +-----END PGP PUBLIC KEY BLOCK----- + +pub FF844A3256C7FB01 +sub CC701C87EBDDEF3E +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBFbajE4BCADV9z1xH9nDt2u0VIkDjgGu2lynAiRxoPy8eysPwx8f863rohmI +3rZOXuBRnWvm+v31P0kLGNFi+107dqpco1KmYJYPc2TUa4NFamUOtSE7jWAJAKFR +vivtDWITIJYPscwvJHsalYhOSrqSgCKw9+kNyoNQEXKKNPBLvMzDtpw3Z/YzJKZ3 +2zrFInxdxBykHgFyD9g4NQONdpFdUCEsjDKbw8lk/CvNTcWsPhCpHzbYlW+thhgF +9YS9CvW5fQ7GskJMlzU0ntvJ5cJ23opVTetWcldgZIczkc5GZ/ivE4r6GiPRA3z4 +eJTvydsi5tnr3WPLY1xxUDYqhLb/9M7jexrHABEBAAHOwE0EVtqMTgEIAMZYLk0X +sy4eA5F2Iz1fR6w71tMW1P/bqcBzmU/Fq8QtomHrlgLxlutLvW00vAc0sFLtl4Do +04uEueGgJKahfUVzf7NmsVoBuKKL+yFCevlWZ515wFSe2ArKu+8aDt1H1qQX0FCl +HuYtd1U892Xd6FqECCOctw5R4T5IfEm3ecTDhnrtKOxfILeWEVP1W2NTjFvRPoDV +NimU6uNZmjADjET3hM5YlQa/sNmhfIzeFqDQPES4ccBtU5FEMXTySeRG0/G5cLbd +mbWGiT/cRBY3HATr3wT2qSCQ9L7AwS7BvfV7pZR8WNdl3v/5wbOdAxFWYutRL8nt +CHL6hyTtyISXNGMAEQEAAcLAdgQYAQIACQUCVtqMTgIbDAAhCRD/hEoyVsf7ARYh +BCBt98SOPWyxRd2oNf+ESjJWx/sBIdAH/jJtbqdswrNiTOQH4rOK08SHvrsexiYO +lLuuti2TaBpr1O9e7TX1Zqcncg3lrzvACVQLcpOocnkBUkvu8VayGBxXVmqZDMo+ +adgtvO9swy/fPHFJlaRhIn5mAsC3x5NQgbi45j/RMm6yq4zgWzdDvbNE3jlUnnDJ +Wp6tjEJnNdSfOtI2O3yAJugLMIRB63MvZPBXJ99iAZFlssnsLfQ0QNol8xUSiE7s +Ov71nCn8ACd87LbzpRYLTggq51QXTVuJ/C/bMKc3qmNhXWinpcY8IDyYvYBvZN42 +uuM24k5O3XykNfr5dISKbvfUKAN1ZeQyk/awXlSB4nlxx5w9h0bpYL4= +=hwDg +-----END PGP PUBLIC KEY BLOCK----- + +pub 012579464D01C06A +sub CB6D56B72FDDF8AA +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBFgnlA8BCACVtx3oLXcanfvwtMRwal6pLQ8IVMG9+fr4xGdbSHXCRNbosDa5 +agU7WeQMPhusSxJGaA3w7NOdjAwD/LeHADhDPeI6llJg1Fb3EyqH0NZaODKU/Or/ +dID/i1onAX1dE914J4lf3XvIAxGiAjmr3UvWO9RiFxRUkecMAMlCBp2FuHuvxkcn +Mk8q9dP9Ef360wu8X5rj0kgP6vPhgl9/RhuPsUxlazb2Kn9Zxi/RmDKDiH/vDuwy +WdRGFOR1OPV7l3Ws01nrs4vKd2v5rsUmsjvQ8ldxdrA1xzX4IszHRDgSC9PI8ItZ +1VlbaKjE0L03acPfFTg/wRFSF5zsrGNbTmq1ABEBAAHOwE0EWCeUDwEIAMGWqQT5 +ccT/Q1OypoOQGEZn+oRkgEdnzt8mjo7aOXd6pkNTkt3+LCkmb8Pp3/a3iYEfvSvB +Zbb2JbY9xnmM8jBucWnow1iwEPxGhUuu3jlIpRsCwLk+utLkMALRkooXqanDoVRW +xuVeFYN0as8nndgWiJT30innN4vfaR3x3E6/nS57zp5IggxZYsXTRHb25kaof9lg +lHyXeypW7quKOP4SeES70PVVUnYZBlLpnX8a2msRtJiouWxCv/kHnYsjW62vc7nq +vWAsSsfBT61TVx7yI9CckVFBnkpG1I8C9WpfcR+j9yauptgUMfrfDTFg3Aip7czM +SoL4Jpu7jBcXy9UAEQEAAcLAdgQYAQoACQUCWCeUDwIbDAAhCRABJXlGTQHAahYh +BPp33P7y7m6y3r7dLAEleUZNAcBqkZMH+gKgKy4nvrXuCly4QBfFZMF9xcqjjPw5 +sF6TZFSHQBj1peNFhLPDBu1UVELTUSyvtH1vlJxjtbVMNAEovQ5JFnePDLv+EDuT +w/vECneYLj4V0docwfycbPYhtSMZaXdinTU1GfiNzyByceepxR9/s9exExS0nd2d +uwhg6sEBtYqV3TtFURBTJp+BR90X1zF7o/+yVJnEBMmuUg+94HluBxUMwzDVRA2o +kv0tY/YgzvFyWM4EdjuOrCqdDilERH3ZXOEt22x3AXQfVK4RGkPEEC6JtyEygJ9D +ccRH4raZNSgnTjGiDsxCzZpozBJt6bUsy80Fn+Z8XtAxh8xXafutsiQ= +=AoHC +-----END PGP PUBLIC KEY BLOCK----- + +pub 02216ED811210DAA +sub 8C40458A5F28CF7B +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBGADx6IBDADoHin1LGQ8dhnlhfNCBZ3IyXS2NpR1VjmYtHSlh1hGsPcmHuwo +1mLA6JzXF7NuK3Y52pbTr6vz9bAap8Ysjq/3UJeiDbf7FvmO5xAEVUhrpc7AEY7G +Wygi+HqK5OaNhxUr7OmHY4N2/NxXiYGD2PNU3mXkOszpQJk3yVKgjmGnv0zbTpn2 +wwsXygc87nG/h2R4YQ80m9UknkPR63vRwPnsTwovG9CAb8RyHq+6P81vKE/U5GUJ +TzV1BDY95niypsCYja2QR4Gi5TKlpsUjT4sT32l6/CqOhcpwO05pTv0fvoHDbDx6 +/gHivgyVUyPbQzUwYfMYoINePOaX37okHQE8n5QPPx6HmXfIhumKbXi6ppVPjPG6 +cB2Lq/F6UKHlttiwWgSIiLDC+UbFCVvc41Lrydbt/2eXoBGxWbU6DUSGnefKymP3 +c3IsgdzeP11tlfaxLVz60lomXMeyyXD41QzeuyzUNvoSgiqSE6BO9EqeCyk1/n+O +Are5EFnyLBjChgkAEQEAAc7AzQRgA8eiAQwAuC4Z9laL4sRX8FTseTzd5/8AqBKk +gtrZjW5onrse1hWpkjeB42qfhVrfUorkpGY9N0xo7jZT7PhXuOEB1WRcJPHA11Q4 +166WkHRDv7IwPGAQr6LsJAAlZYkV2d3BXoWoS4ATCH1jyXaxKT/jNGBazs+Nqprh +ypL6X2xOIqKozehjTMfD1cFzFzoaZvD+G9qdk0w7qikUIla0Y3ADswtMLH32mszw +9g0ddFSimmWQ8scVcaalt9k9ATX7zMJKmYaYi6fWsH/Le13DhJgQMjjh1BeUguIP +r6pRoBZ/5xJxJ7OKIRk4pk6h7BImGMKTCONICf41i4kGsZMoRb2XvLDgSNs9gYKp +N9+J7TYTeqofBxxQLH6cVplBPoNCkJun6scYJLWAepr4u0K5RTnU7y9iigiTTFeV +xbSjuxIEzLk9gVKD1hsbtkLVmkxMljqJG5El3I7qu7eM2c1ufo22BFjHom1CmtWd +oai56nxG5zv1WDsMRJukaXbDwbpSkb45rj09ABEBAAHCwPwEGAEIACYWIQSFaclc +rcUIsJ/pDzACIW7YESENqgUCYAPHogIbDAUJA8JnAAAKCRACIW7YESENqpGYC/0Q +NoVAXMkCa0Iei/kGdzZNLKpiG0nZIJGuml9T7eMyp0QQXzenOahCGhna4QQvSBER +UZb9HzP/0xY93C8FEXv7Ns972XdeOvYjpOLG6euRwWLD//c5Ah7siSgUJ7CFPBHj +r9mnZXzYjhvXT0eJlb96j0rBuSblG/NXu1oEJPySqP7vkK2ZZsHNoGfSoGlGtush +YtUP568KMzz4LsnOfSLnkOc9Hh0qydipY+ocfQQhh7tLUzFsMbG80yWw4/2JVicT +nTosdl4J9WyI3Xuqa423XEAC25dS0aQNeDa4lpfmOOyj5ViJISdutlVC3zmtkpXE +xUXqb+AcsNDOuulUhVjw7KpKX7xUXJM+LSg57lfyGHiLejDHvPAXBSfzFxT9ZDxO +92MhvR7JqP1Z0SvZ/yZ1RAidKaNJs3o1Dk/WbuxnRYjyf4URhfUVeH8tykNDIMJr +gY4uKjJu0S9RuzG1PVw85w5f6UDZlJ01gGvtT81JFrizhvS9t0HoPbDcDhG5iVE= +=WNGv +-----END PGP PUBLIC KEY BLOCK----- + +pub 0374CF2E8DD1BDFD +sub F2E4DE8FA750E060 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDiBEmoKU8RBADEN0Q6AuEWEeddjARAzNXcjEx1WfTbLxW5abiiy7zLEht63mhF +kBlbyxEIRnHCSrPLUqY5ROWdyey8MJw+bsQn005RZmSvq2rniXz3MpcyAcYPVPWx +zgoqKUiu+pn3R7eldoDpMcQRsdNbK4TOFWNUomII70Lkj4u/DP9eko6xowCgvK/R +oRhshwRoxJl1LauUFuTeVHUD/i5DryK5j/P9tv9BWSb/2Jji6gbg6Q3MThZ+jCTi +leOHR6PSqajYphOFaA8xVWQAkvbqfSps9HnmdFJ37zxOn2ps9d1L8NLoX1GMu7dv +UZkCY5hR4gwaAk5YpyKa93NpaS5nX6beKiCes7lDy7DezjQLZVbKI3Vsd5t70eTW +tD7JA/4lGUSkole28jxo4ZKKkGMFnAXkV5mWeOTz14BibW7JqhkiIpckDfyq4NjK +ts1EzMmnXmPkB/u5QHHe6fJP+Laoe//gP3Y5+xlnAsyI4iEfEjydJWiSNx48c/2l +qCQ/wdOb28xoFavdCCBavqSKXKJREHXul1UGMICpu3rq9EOk487BTQRJqClPEAgA +0QeHyW6OIAnKi2f9oxjnsGli1YfeJrnEAD0KxhwzAfO9eB4rk5gCj2DJ2IQ2vQhn +FrjcCdnhagn3oActfc61cmGvyN298QeusekfuweASCuW/dVjDYdlJT1yZ+/7K+IL +sFKtCprot87BJpaLODlk6sIbsnYUAqEKdF3Brxk6zY/T8+7pqwHgbTeadVpHrZlK +Ge0XHiJJaU7vxxopRBsHk6AryhgDWT1gDgRF5LBkyUpal8Y6qDAcbD7G5GRdQ5vO +WFpNa99eA+vlGzFnMi+IofgRdJ92IinZDOpmMz92uZ8jH2voCLb5zlYo4jK3RZpf +QdY4ayHW31sE+zYWus7UfwADBQf9HFVVZi47bQfyhHVunnOSOh/CBaTu3o1Jdm7u +ZkxnCppGDHuBcHz0OriMAvDjFewBZ5uBhp1F5Z5/VlJSXHwvPUwo6KQICV3XyW+p +/+V++seL5kcic3OphwB1qZPYEqhceEghHmN/r/wWV/8WxkZ7Sw1AnDwqXTJiIZha +EjRVXUIjN5WpINIssz+DjFnTu76S3v9VSOjTmUU7qPII3Eg7dJEgE0wv3E1d9lIP +PbUa0pba9735uMLqoQNrT87kXKSjKhQUD0u5bu3TmLdPboHzUBWYH/00zEodwkjW +K1TxZ7sv4gC8oLXTpyHDhLGFdjFr8bp/FM2WQ9Ip1w8ax0UAtsJgBBgRAgAJBQJJ +qClPAhsMACEJEAN0zy6N0b39FiEEK8vdDyPqHK/MEdSGA3TPLo3Rvf2rkACggrRV +JrJYqCD0o2ZFlSyaaO+yKrkAn3IGGwB7ArjBZB5GdaGUAP3/5Luk +=DR8j +-----END PGP PUBLIC KEY BLOCK----- + +pub 04C8C902FEFB1A01 +sub 741DB70D5BD02528 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xjMEaFhhgRYJKwYBBAHaRw8BAQdA8Tl+3HkVTIQq7QvPzpgZV6i7/z0LabiWTt3G +fH8CKuzOOARoWGGBEgorBgEEAZdVAQUBAQdAaPAe0Es6+EY3/PwrQP5FynZnJL4x +BR9vZ6+Z3pYRaxYDAQgHwn4EGBYKACYWIQSvdRKvZv5ABNhN7IoEyMkC/vsaAQUC +aFhhgQIbDAUJBaOagAAKCRAEyMkC/vsaAX4YAP9RAqd/WoIBEi2Ix9DMStr1mOCF +xF+S3TSFWPLVVVoo+gD9FGYeKnDDga+F1O3zW0ghkiaYxdpuD21/ySNA0+W3/gc= +=CTOR +-----END PGP PUBLIC KEY BLOCK----- + +pub 0729A0AFF8999A87 +uid Kotlin Release + +sub 6005789E24E5AD1E +sub 6A0975F8B1127B83 +sub 3FF44D37464BBB7E +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +BedOSN7DqsnvIUfFaQ== +=bvfg +-----END PGP PUBLIC KEY BLOCK----- + +pub 083891AD4774845A +sub 8118B3BCDB1A5000 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +8yMyA/hAg9CXhbsgTGSN8vGaIOcNQntcJ8w= +=4idO +-----END PGP PUBLIC KEY BLOCK----- + +pub 0C0A9468268C79AE +uid JetBrains Markdown + +sub FA258103C03E2F42 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=KXLD +-----END PGP PUBLIC KEY BLOCK----- + +pub 0CAEBC0883C5BB65 +uid gradle-maven-publish-plugin + +sub 0E6A26CDAAFC89CD +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +FFj5 +=Qgps +-----END PGP PUBLIC KEY BLOCK----- + +pub 0E91C2DE43B72BB1 +uid Peter Palaga + +sub 83552A552A0D431C +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBFBIm/wBCACgqvegptBhfKbyBXZiW+7XchIJCOpwq0/9QgSehKMwELbUKqNM +sIVrywANqYn32S9hNRvBiKGm/KY7VwN9p1Cr6Ey3XuGSbRo/xN6tqfV/rV5YClL5 +6sMc67BlnEaCZRNuB9ATeUE/4wCO7fWg79jJuNl8tKQ8EYIrVGizzjmZHt76OwAi +hQtD6A19+qjQ02SyPUJS6a2lKx+gwaHNxv4L2FqImCFGOOEToyRb12GD18Mgbf5o +OtQVVtr3qbT07odFQt8Iyy1DiNUJbOfC+YO2wO7eMTr5xaFr1HejsTvKZiTDC0Nr +EjtctqGxrjxPmoUPNwtxwEDTEh1lyKMhnqgJABEBAAG0H1BldGVyIFBhbGFnYSA8 +cGV0ZXJAcGFsYWdhLm9yZz7OwE0EUEib/AEIAMDUgjnPKBeHIN0KNmXTS/uXXC4L +TGltnQJ57OG2kmPz/JjAjYLoLvINY+xtghehMhRY3DmQDy/ufZsgO9oH8PztcC8Q +L5/dV6VTYf4U3FndbiSKgikaBX7yu5Qcrtkv8XgkJ+awIEUgTGDXn2VT1hH6yEG1 +tA97iT/d7ZUxLEBsVgbxz9VtPellTNK5x/8NGY4NW+fM6+yGFpjr5juZVYRLa8u5 +65vGBQO5FU7bg/69DftmL7vO4KRLs154VpsfAsTeo1rmU/8kIjgCVeKFClJG+Sg+ +m9rsJNYgiKy9dGfD/qDmVlEeWBuhtlAfqM7pHTv1Mu8mv5/DheBwvlwheg8AEQEA +AcLAXwQYAQIACQUCUEib/AIbDAAKCRAOkcLeQ7crsaE0B/4/+ZcjdUfLPlKk/8BH +0tMafEWOGvqY8bG4YpxGoJZHT/Lb/cnWDLvZzs98FVaQ3DKHZwQhhtnQIhnupvxS +HX5wLeBZMtAANGQLauGp+A3S1WBVRHs0mzOdlVDbzJu7RW72mnkRMSoVd018fh4e +Q0+VpZh0Pf9KfKJDwpEuESP1+6JcLLBvQXlEJYHOk7Up5eRkhljdIwz3TlSuJ9sC +scTgM0PI7/L1eFP/iCgZIBHhpllVV6v5IGXx3P5Q7YQUy32zCrht4t9fdtdLct1j +6eNaAQdPAU91auSbYhuVCpjgKNpwOv1ULoSWLUUPMNW5Qc4ZDKq+ywOElvONMnX4 +oaQ1 +=uNY7 +-----END PGP PUBLIC KEY BLOCK----- + +pub 10738244228EC1FB +uid Lorenzo Gabriele + +sub 02C11E7777C31E67 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBF+MJMQBDACpR1FBvCF1P6HnZ0qNusnMGazyVyzFWP3UMeaFnHbnjML3tfY6 +6CS4VLzVcnb4/sMd5rmClLLwC8aUhBu96//P9YeskcNEaL5oWyvoX+Al57OUqvwf +AXoR8J8U1iF6tek9L/2y4IQWyYytYp7mD4ZTsUQm+nTS0H3+xvTZ1r1j3DZjtu7j +fD3L6rYUrRkaxJmsPuHN4MRhoFyxxX7lGLSIV/D5hXIRm8v9w86eLIN4TkV8yRge +kBJo5KHZZ1S2FXnUxDP7Nt5pXT6Vl6S86uA90XSrNUQZxX3e4HN1Z6XfupBZrqag +JF8UO58yg1302ZYdTDxIfeN5jMS56gsxZO07D2mwsujU6KCqqPUhei0SuFs9kWJb +XEOteJDgZO/ZC68gEmf/iBpfbN3VHsQYfpl5EzFboh7OjxMrdsvAs9XGiVUnKW7f +XRg/xsNsNvatFVI0YaKd5I/H65cpHCgXeeI/QFkFxcejuXzVftu0AzVPIaJLxt9c +dsehnbUGNjQ1mQMAEQEAAbQrTG9yZW56byBHYWJyaWVsZSA8bG9yZW56b2xlc3Bh +dWxAZ21haWwuY29tPs7AzQRfjCTEAQwA8hsQcv8Lj4XVdQ0mE9/a0AkYMDdGwIG7 +jAYVvqmTqWTB+uNTPNA2qW/F2hwjT9wyqXgATNlKkzG6TgCr4AR322d9WJAqnQ0U +01R78J/Bqn+ZAGjOEEVMHer2HS8xCkoVeHIA6Puy4Ng7sKJbqW9wE74cW/Eb5Q0I +3SAwz39gYWFyqyB5J77Z29T91bC+aSejBi/eA4Nqk1+TvqIJwKtl1ZcXQrAu77tQ +TC2+fTaGysVThxJ/iWNNy8P0t5yZ1zCBz0aDo262RuVwdcrnEGImIIl7oJReJjE2 +50uJoJXo+laNFcd/a7I5lpdrVQdQ6K/aeffgaua/VDDeavKnyJy5JGZU+hIYmihO +OxopowNUez2UAIP5OqWjohOZ0aN0+9ji/46TJPwa+ok6lskaw+vvY+8P4tX+U5/J +pKSD7MTYWc93IQcbNoae+9bfLdFmkpPw8v+5N/5modhUZLe6wPKVghnBy9KM6Axy +SEh6bI9HuzqeSMyCooTx1bo2epzuNTn/ABEBAAHCwPwEGAEIACYWIQTj26lwLSs3 +D9rCW1cQc4JEIo7B+wUCX4wkxAIbDAUJA8JnAAAKCRAQc4JEIo7B+z6RC/9swDro +L1k/bn0XExT1xDThcMNauXgdZJRebPayBGFeG7Ks6yz+LzM629H3TPmKlVSZvL/c +uUOpF/6pHOuzUNcMEG/vWScY7M+Mei1ZEvU6foUsH133Y+v398aQw13jR9m4C4ST +7Fi7TSVwlmqR8woHDlIjQFQcd1YxN1YN6ulh9QcqrdQvHr92wUzUPpYtkVx+Bkvs +atQilZXCNtrn0bu1A7Cx9NLooQJdSAAa333LhYFJxPxuO+OZFPSEAT8iBZ1+d6kW +026CynE0NhJGIZEX4XjNpYp+NU6aeb/8s/tBrDPDBhh9srGh7nNcSKZppAcXUj5p +C9qYBcSoBfTKIvg3kSCKKIsIZcJS/deJy2xKU/rmqttke9tjai49yRqUo1D1Jk+D +xhxEQnftpzvNJvdjjJrqP5wIzFkUvzSXs2Spg/Ugh2R4N7SrbhXJhpHK3UCXluKM +xe9Tv6CvxnwTzPX8dTj/tu6z+xB4HJVjxi3DcWy6LsAlQ7EZrjMSxIHL4PI= +=N0gc +-----END PGP PUBLIC KEY BLOCK----- + +pub 15C71C0A4E0B8EDD +uid Matthias Bläsing + +sub 891E4C2D471515FE +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +LDL7A4GNor2DcqTyOw3cjy0= +=dXml +-----END PGP PUBLIC KEY BLOCK----- + +pub 1939A2520BAB1D90 +uid Daniel Dekany + +sub D068F0D7B6A63980 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBFHNxM8BCADYmt+HKkEwu89KQbwV7XIbgwZSfWc7y1HvA2YJpJRXJQsU/Pzv +BhsHnm9ZIScBLIlgE5OUnMNz8ktPDdsFg3j/L0HREXOAqkOFxWx2kANsRo2HmkM3 +67RAu42fJqJcjD2Rs37wMxlSRRGQ+/bp+Bw2HNO1pw7GwrSgmZwzwT4+1pE/TvXQ +Wl+Nhdf3swLyBaSuWHJZT3+JOR0kEGSQuurR+57r6fKDmouWSwAKn1z97JelHuXj +HKZeueCkQvX7dayPP4a1zpoXPcoZhYekFarLWJl411EA3aHIIV8whknsZx/lGGC5 +yF9AVIzHHnhqFC/Fr+GJbwa9oMFXj0pY06ZNABEBAAG0IkRhbmllbCBEZWthbnkg +PGRkZWthbnlAYXBhY2hlLm9yZz7OwE0EUc3EzwEIAK6rZ7kRp3uj0CrhvuTnLHU7 +nEs+KvoUZKLyhcIys76sJQ7cnhEygcG7tng/EtK8bI6skLwUaF4fnPliDj/yIigY +08p7TvFL/6HL4cLrIXR9uZe5IdvBKYhy23Ie2JXdLk6zH6jq5+vBE0IA7ljJUQj0 +PgiIL92kB73Bn6dPayvtApzctajXvGajYNfOLTYc3n1L/Kqay+/UwjB5MJVlmFtZ +1a/EAxyb5yHld/s3RKEaeEIpjaoPSJwXKOWNAcLdtgcPcsyfrV4bkgjx7ABzPvf8 +2gYucthyIx4zPZ29hZfktSV61h7cbJL5HGrk39UcSgfstHbfBQiTY/1kVN9tuHkA +EQEAAcLAXwQYAQIACQUCUc3EzwIbDAAKCRAZOaJSC6sdkEFjCADEzcJtTbykHeSP +GykEtUnApHYM8oZixHWFvDdjkGhePMTvBRJpByS/hdS4Mnb2AfBoV696eCFAtm+D +6iuOA1OYgc1CnGhilxRVpzjgbD0S6bG0tyiKz1dk0HKkGh36wumST1bU2qdA/UN0 +CoRIA9Csb+mg+h8c+y3QixjbpTSS4shhXpzfj8QsZmPn38S1amaSTEv8zqF8pArP +U93184TQfJBPrjAShTEitAmX3FQlSL5v5sZms7T5S/kOHkcHm4zNlwXRJ9avqb8k +q2rcDJX4sCe7PjoMX3y2mTk2YezY4LrYbhEeOGcMNg7XOXlhtBBJ4OuqQtXo65Lc +T7dK1Uyb +=/U6G +-----END PGP PUBLIC KEY BLOCK----- + +pub 1A2A1C94BDE89688 +uid Michael Osipov + +sub A3F393B5D034A0A3 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBEzxj6sBCADGV4szLvjBwrAOKYWw3efASDI2yo5Aq4oevm9cUB4G9G/D/fuR +XhodLaG2smZLd8sNafWTSbPHswsZtMAjHGzka9Uj4Ow0etl3+kTh0DE6Loezkj7s +nut/6JJ8RGmLf+NqJJhxS6kCCAND8GnNIu1gGY+nZ0rVO7ZkPwtUR1H/MnoZ3cC1 +6Ual63UOjgsNhmmaiCFyedzxitUVdGqeYktPt/rp/NqJ5zPs1SLX9vbFNTQ5iVKw +EszDiYSOTBSZ2kVlygGD2JZGIa+uQ2yGqVJthXXlcG8sineNJAPnkNyW8Ie2uYeS +VFgXoFPJDWXYsFC4APNIAdV2x6+OZybsrOzNABEBAAG0JE1pY2hhZWwgT3NpcG92 +IDxtaWNoYWVsb0BhcGFjaGUub3JnPs7ATQRM8Y+rAQgAvSKl7ZKtn58HID2sKpOF +WSQMu6+vE8ce3avBmIpyOGmftXyneVo/phWjEmpcSPdYaazoeNXwFlr1NOEdpDI0 +5mxtyy0jELUltV4jxr2l2mqO0G0on5yDw+IhWL94ddmiZxfywrpUDdQu9XINU6w1 +ZAzLOYQpATelBdQ4ukg5/MT++U0SeDakYafFcdKkGfUUwYESU4iNxM5rNcjl6RVn +7NlxnqQIZm+dhAobfSMVrKbKbFxHD5KYh0fcOzxr5JsqSLqQ7II0+A2lGErvz0hW +m77bJyDpQcqj6QC9iU9ID9+1u8ybLP4j0bjHZlRMHQOxqUjXjCSLTgYbQ6Qmyyli +hQARAQABwsB8BBgBCAAmAhsMFiEEaoFLH4acK76rfLcnGioclL3ologFAmZgeL0F +CR0xUBIACgkQGioclL3oloiR0Af/WysDYljLY8sWPKipeGeFKVco0cPtLg4uKKFM +caYj+BNK5p/82BAQ3prgxb5qTwD1ojiFERfRDyTbFHIdqhW5iizphvXACHMt+WrH +tBQ0Aq3cgqSak1/CvLtAhgN2z94U8qqhP/vTygNZnbMz2LdG1djvuciWH0ZjOnhW +jR65xGNhIZ3WyzeAVlMGi8nVRvpJvADJiosKFqjqxXf3ltLMs/sqyuCMxAXD+Lj4 +J40HFogDg+MmGIkuMHb3wodeIEZBPcLEwE4LdSF4EV1at6hRO5w/RnY4H/miyHzY +c0hJjOWbMfM2U3kun3wmLazfdH2BUb8QhwO1ZfDGPZ3GNjp8lsLAfAQYAQgAJgIb +DBYhBGqBSx+GnCu+q3y3JxoqHJS96JaIBQJinGm6BQkZbUEPAAoJEBoqHJS96JaI +yioIAIk3qs9E6vOhEZicQdbw5VDJ/0YBJ2Wp5qQUqV4P3pK22AP53GiRQE4gMOrO +kUGGn3gDUGHB3Y0jWJKScYO78xSLfQCWfRjfVqDIZP8Eq/T8+klbw+CbWDQfBpOx +BO9GYV1tQMe1YuWJRQqFpMdTYYiUmEXnRv+rYtA39dbpUcBVHen7S+wHn+1Iy+OJ ++ug0g0RAunlXbnL3JLTstIIz37HDUbtpf4usUxh3V/mevYeY1UbNWPVXZVQtomAV +zEHRFF4THi8PBQRDDB9bp+HWW97wDWlrdEMis1kUG1qLHtO0qUi3WpJB6uyPuCxS +6xdEoXWEx5a94ARTKMocpZrfYlHCwGUEGAECAA8CGwwFAmC33FQFCRWngCkACgkQ +GioclL3ologplQf+ORhN2Uys7Iaa2it3v+aB+xqb17noTXxzhnkE41/jXCwMnKbJ +/79qFq93McdHxDDCjTyz+UZuaDg1hCoILNuNDWS0PtBzlk1yaPuJPkEUpHGTpSkN +rrlgeTUi6YbbimBVB2ngvAO91X7TDJQ0GerdKbKOpNjicPOoYKRXBcy4Trfq8Fxf +SAZSZC5tmlHLzmEYYEHbNsuh3aVjVmwVtS3BRfBI2Zn9gX2CWUuMI48tLAYVt+Jy +kA6buIcvfG1DjAsQOj0sw1/fCc3zppIHe6F8bWcV3m7gqeTQ4Gdjkt7glF3kiLxa +moAYFrLMPPO5QX+/xcfbNcuIBk3lld5wKLWrdcLAZQQYAQIADwIbDAUCXtFnAgUJ +E8EK1wAKCRAaKhyUveiWiM1QB/9qugzb8U1BW8NYaz7May9qs4B8PLXfHPsVtjJB +hDGTs3ZxXt9+BTfQTRmtdNTMER68iPDRLdtCzTBfNZvb8NmpeFIkL/E9j5O6UK4s +40x23lqJ0eiSDUHQHZeWq7m4S+J2VALA30jeDXRoHYgtbkrIWxoNH7mC+5/kEQMF +Dj/Kbh2n/4nSQzd0T8+v2OmPikBPrZ33uAt+aZx8NKck502BVwezC5YOXaIgZvIV +++4zfiApCXJ+wx4lHItFRuTUIGgmwO1bjjMWTDSiMI/6pHCzD8fQUCZ/7GezvatI +MR+3PpQiE1Tr7ToVORYPY9neaT6myy4PbUs1rN1j9/RMmj5uwsBlBBgBAgAPAhsM +BQJc772HBQkR2MncAAoJEBoqHJS96JaIC6oH/jnXBsqqk7x1MzYY5LM0N33TOVWJ +/mECxufILyi3YU++bbp72hyGbvbwwVrJfEj5OBGpW2vl+ldowRn3ohx6+ccIL3t0 +ZxNU0nszs2FPCENmq3tQCEPX+Dqyr3NjGxzm2n6b8YHPMDtsOxH15OZRASPCBbFY +A/U+M8U7rqqFptH3YXsWrf6o9FZG02ZiWX6EnjvjxcxTCzC106YRirFsjzKzksRD +lA3A1zG5m86FcWtpgaEop8fP0Cdmzdeb36px2RMPMB9jhntdWYw0YDXjU+QCCQrN +UnqcpFFTU0MLcpoN7FQoESKxzqiq5JPaeGRFCLe4YoavLIhFgpz+BuR5z0HCwGUE +GAECAA8CGwwFAlsF2O8FCQ/1fMQACgkQGioclL3oloi2pAgAim45ptLM+0l91EEL +YkD1WbBo6LpBlWu985BYloRSdpA1eDWQxQTzBy+LfyUCfoNBtUSqt4yDl9WQ2XeJ +1ly/F9/oC9BmBjxFi2pQwcEb8YFenzfmPiTTgx8j6ewqYnnhPbyCBycp3gKcqo4j +Q6RjAvtMtSJtuRvniPLXiKzjNYx6/v2W8Q2rS2reqGy3WGrz9AjaoCD4nwCsqgQO +4i2BBKvgMxnFxYiNi0UU9HpxZGFL3EhwK4x8U1uwXvIw2v8ffI8oEy4G5oV70dS0 +gNoyaI2CadFqTrExOUqq4M2qwZCbudV9a8uyn9iGF/FT44orzTU22/559YQ9Bb9k +RR9RqsLAZQQYAQIADwIbDAUCWRNm1wUJDgMKrAAKCRAaKhyUveiWiJ6QCAC7qDzE +dv4jphwiQHB1ryHxtu0BMh4kNX+mIim4v1yWiBHDsJlSnxaABrdii56LNdBTOQTj +7biT0Frwa4dQpG3tclS7l4bhKWXcO6fL7E4Z7qj8xwlHN1QQEFZ+xzKCzLA+LN0B +3qJ/NVgpC7S+JqiBl36Wtp0znVZxe6PKXwQ06epVnkquspwQLKLBa3sI3u8DrekY +FWn2B9cJ4yCTZYgGj8Xb3fkMdxyFt/edkimcJCWm6/QRSWC3JDBtQND5DQtC8NCr +Q2dENvpd7z9jVBgRwRN+NmGZLPZNYN2ioi0JMu0rTkW549935mL0ixFE/cNZWwXt +U0jiRl5Alo8HyltfwsBlBBgBAgAPAhsMBQJXNwevBQkMIBQEAAoJEBoqHJS96JaI +asgH/jxCg3JtOU12aBJKtZAmo5UHcTPsBUkCfB49cvwpwwCk36+txqSZhLQmwUmM +QP0Yvulm7k+YhWyAtYK9JHFUt7tTxQCaKNL9Hv3g7vLLVEXI6i/NBztonilJb9El +zBY3uWwtjRvb7LtQtF9BqfYhGxZ017kv1JbaV7+8kHbi5+Zd7RYQfdMYSLTKpY0J +1gDzCPi321uIHIcOsGbYaU/ZA8Uiqb6EYxLPS3UcyBOoU133f8cv1hscLtNjqzO2 +CVzRjgqBGP1Zx9rLYPgD9yzvln0ZlM39OvbEoUPILPDGIpZwGnFkLwoHOJEgUT4/ +nhWiUL9FEk6WUJW8iyVK2d5xAanCwGUEGAECAA8CGwwFAlNc+ZEFCQot0OIACgkQ +GioclL3oloicoAgAw1z+LdwSxXt+LTQM09e3slTmLZZuy2qNGNqC4RQrL5iEZ2x/ +U1pLhxhxplH8VxP8Rm6LpsGcQ9IxzFrz+IXQpeGRNzxW+5o8ERXvDzXuEfMc2uXB +95h5N12HhhxxG16z49Z2fHq7P2jFlHzQ2BVHfrQrB2b0yYTfqe1nAI8dpphA2ZWx +ncKK6ISG3hfLsVbgzfw3Q8TK8cvjZarPrBLT3aR/MBCniV7/oKnOoCrs7WMiJPmY +YpFFTBVtQE9adq8yMi4NUYD5ClnrBtpAScmjzQFS276RCbLKK4xvcIR0iJ1vFHwf +dYIWJtT908OOYgKULmMmxxOOk+fmF+pTKT9Nw8LAZQQYAQIADwIbDAUCUNy4NAUJ +BcxcBgAKCRAaKhyUveiWiMDtB/42zg1rFFwYC7GxCXjg5B6F5BCY0jYAhGZ5esWM +z9B8kNBEXU06qZ/iMmTobWDvg/OBsVn9JuOdA14ZHQquvA5+9REgB12v6EGZvSB2 +M0LQt50aeqMMkJPrbEKUtVbHlsCJGCp8ABMt4Gmkp6kfAS3OKcWHxF+FINO6C/xK ++6LYQfdZ38SmG+yv41OE0D3TvncbhvhULByJ0GEHoQ73R84eFi5Fr5pTyzwmaD8M +z7IffYE/sxpSF3c3l3bstpi9VWTYgnqvcAVkzADNe8sxQTPWqcykQJbZuO53LVL+ +TaxNJz1qvtTaSf9ZDIRhajFnlPSUvNax1GdbHK42O7H2ULmrwsBlBBgBAgAPBQJM +8Y+rAhsMBQkBmLEAAAoJEBoqHJS96JaI0Z0H/RrPB0/oetTbeHKMqwkZqfl8T3vo +qInt/dShli2BOp7X/UL0NLQHCFYz1pkjDlDLMhpN4kTI6/7OK2l8ha0WeI5hgNlA +nFQcuRbgwrjerDMlWXAW10tHAPptBJdvv3y3+I6jDj2nMGsOSG0f8+z55mrvBIVY +FD1oraEEzCrfmGnubsVbsy8vbvxpR8Uo6x3R+TIMjtJ74fXczC+gAUBdn1aLYLve +l6J+kF1873OKfFaIzxvi1I44gbfxWy0Hd6wTX4LEQ0JrxbaOIIpb0RO2xP4w51lW +nTkUyTmIOtAfpcNxqIzJjYoir41vgaegw20EnoHgJiCD5OB6Xzn03h+rTXI= +=t0Nx +-----END PGP PUBLIC KEY BLOCK----- + +pub 1AAAA7AB37293D3E +uid DiffPlug LLC (release signing key) + +sub 2C7F998F4272C851 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +Eg== +=PrQD +-----END PGP PUBLIC KEY BLOCK----- + +pub 1DA784CCB5C46DD5 +uid Rafael Winterhalter + +sub A7E989B0634097AC +sub 7999BEFBA1039E8B +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=1ntv +-----END PGP PUBLIC KEY BLOCK----- + +pub 1DB198F93525EC1A +uid SonarSource S.A. + +sub 2161D72E7DCD4258 +sub 63F1DD7753B8B315 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +DCeDZ/puLXXnFjp5Zw== +=/ABK +-----END PGP PUBLIC KEY BLOCK----- + +pub 1F7A8F87B9D8F501 +uid Download + +sub 7D1BE4480B61E2A7 +sub C2148900BCD3C2AF +sub CFF46EE3C17E53E9 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +Nhg9a3HXnE2l2ony +=mF9m +-----END PGP PUBLIC KEY BLOCK----- + +pub 1FD507154FB9BA39 +uid Tamás Cservenák + +sub 8DC6F3D0ABDBD017 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +EA2DgG2xmo0JUOORyytRnNl2NeP7QMqAc7I= +=CY+c +-----END PGP PUBLIC KEY BLOCK----- + +pub 29967E804D85663F +uid Eugene Yokota + +sub 37890E298D9A2BFA +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=OEKC +-----END PGP PUBLIC KEY BLOCK----- + +pub 2A742740E08E7F8D +uid Terence Parr + +sub 74C249541619FF0B +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +KuT+DCAd1uFvcTw= +=i1h6 +-----END PGP PUBLIC KEY BLOCK----- + +pub 2C552BCF2C08FBBC +uid Li Haoyi + +sub 21F51AB613F1A0A6 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBF48IkkBCAC0TW3xE4NIPz9OmrnOaTNEBPVXWgJ0UzEvXh+xEmPQdRkseAXa +z/inCOYMKHiBHi+/QYMDskp1SJzhBY02VQ6tG+VyJWrDwO1jB3aEdYPMvWg2jX9i +8Dj/hPRuOjykEcZTGoDGfA3gcWWZZ9QfPbsCw6hDS75Js3gbJoJO+Mtc2/PXTvbw +A9wAm0EkNVrNc1CIVDqWT39/88aSI2f5D7OsWRtsMtSd1ZBGo7uEJiJV7tduAMGj +TkaZ83mTUv+zKyzSu82IeLoDAWX0hNXqyollheVKZoeRNVeKPNrPaNL9aMHB4Mar +3trt/aVGK4ozchv9o6gQ1ri3aZy2pPrF5MQ/ABEBAAG0HUxpIEhhb3lpIDxoYW95 +aS5zZ0BnbWFpbC5jb20+zsBNBF48IkkBCACxgOO4cBYsQ2p4279VvAFwJS/6A+mr +pQ5plz0a+cRL3roYRCRPBdn7PYlam8zhHsqDElKdJTt81YQ/TYb6kiYm8eznU0Mc +6+klQlsqBkOO+c3zXM+EHlpPQAdP6ug0maj9yMh9R0Z3ZNnpiSGqwy602OtTbT6i +VZvFRKem6vsvqpbeSJCVWpQfJGAi2I8FSHvuGVlbTt2b2JwJtJBimP/p/smk+CFA +ieqBcbTgSzRrQueKHJIEDwS963yatQwmiiOiCgyKCOnN6vVmpxV0ulkpxrOoebxs +U0kcImjODfUHl+T/kXt4WZnrhnPYkUBHvekZ6JcRV5M4FGJLRqIDnxzfABEBAAHC +wHwEGAEIACYWIQRC+E/vzCcEJjj+nCosVSvPLAj7vAUCXjwiSQIbDAUJA8JnAAAK +CRAsVSvPLAj7vN8nB/9OSOQs8gdQz+lJB647qzm0WTAoS/z4u/Qq+R3eG2RSr4s2 +2hOkfZZTAf9jx9N1a2Vnh08PBwFa0hmdbWXYpBg1jI3VYN94ZzyaFD3ySZeARq4x +OSXOX9OtyvIF4f/N1NWXVlnI5iSZMTmZ4lO95T0hE0vnE79HPGbkcTQ3vJekJ7Ji ++ktmTaCzGzRO1NE4m6kHA5KXfJujthKQHcQzo142TrGphY/tCWS78PhBrfBwwM0Q +p2Q15oea1jRfDY0Vk2YzXEuGHQsjEv+ZqffAguXxU5fIrUAhD3GhOi+jL7AEmLUK +3YD4oxnJrq9XN9M56JCTrwIfn2w1llcupDawOX6w +=hf8w +-----END PGP PUBLIC KEY BLOCK----- + +pub 36D4E9618F3ADAB5 +sub C4935FA8AC763C70 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBGGiftwBDAC94Yhhh/5yO8jYFkg01MPnooXKZEPwxAbAg9wn5iM0tHxhEpkU +zJVYZ+JYq013+Ldp8Of7A/d6hKTtZ0xwSeY7S/WFykIk6tc0P5j0sfFS3pGPDk+W +D3DwUa+8m0PriF7iA57vCOE51znO/IUIA3PG2YAK6jv2/i8MDXOOq3qB7VrbvKGB +kIPubp5PbjvP+LFhLuUReU9m2y/3q9lNFXdd9kE2iScqGmu3FDhRJxBK/WQ2kqiv +sJZjAYeHEVNcc88Ah6vXI73uYrvWVGCErzswYy9UrxCAQ/x2OxUdLw7NTHwjZSYC +JvH5JPPTlDxMgfwTIsmaECtw4QgiVmvDp+RVa9zyrdI++RNr0InsXv9gWMv3p3yf +TF20ZL8znFYVUi6XkeQhZjT4fHwDqDVnxhSAFe3E0cwHFJBQe2EFLljwNy6VYnio +wBr7HrAxczRRqlUy4a3bH5KwiNwwvxgqfdMj9KTVpP9t98/TA36bIohwGFRWB7W4 +i395S90NsTbCh/cAEQEAAc7AzQRhon7cAQwAtPmKcM1/z8sMJnt4sHe3ndXsOdSq +TJbRkAcdyDO1F4qgj5z9wkrlVVKGuVtmJS3qmR901Q+oH+JqM6UeGqhNig4IQvME +iQjjelvKXMX9PPVzlP+ga5Y1/2mnUmgmYXK406CU7aaQ3hs7++XDonnQUt5nWF9d +XT+xK/SDLYMk5i1TNqPVFZBPm44HpIjKGNJXD7Vv/5z62+hKswpLXgYt8Rz95ByG +ncjQ1Lo2M1T1Y/EuwlRoc9RTdyABavSQWVLKIz6kKM4LejajjRvLnybMUug0CJl5 +mni4cHXx9t0pMlG5DE2O3mZLwTgWcJ8cu2CtPxA9iLfVvFAThxk3ZitkEhChBtG9 +/V8D4DiTIht6bd49xkHP5pxtB/fuo9lNb0axSBaOAeant3KA6F6vki+chnGhOFqV +1KJHcxYG7VsG1hYhy5IbZsg4GdcXfTwwF1/mq8kvHfyTkBy6HMDGwpr0ATNnrxO7 +tJTiVqDuxfviGQUjqJIQDns6fM9BI4OfpXyjABEBAAHCwPYEGAEIACAWIQRH62g2 +JF0tQOid+0E21OlhjzratQUCYaJ+3AIbDAAKCRA21OlhjzratRBcDACCfhsaCFvM +JTls5lT/dcTqSCYJYZyDj95DlTiaRNkXnAGrTyE45PnmJLv6FFZzSZdu/eLE8ls2 +MY/KWqnZYYV2Mct/pwDDLSjdAN/NSRe9HeAh2OS1kNeN2SIcoL55gEodKBNSMisY +9N3ylLMxHZPa5LNBo+j9wftEaVi2fTX8LDJFQvUOZ3f7cz3f6u42FeHUqaLm2alH +QSkfTB3yIu8Hmo2EXHh4UfwTmS55OBGLQ91d8neu7PcimqCeadeHW+qY5g5hr5NN +LxMA+n+vwPGcQNxg0lH2XBdlFBbAELEGxMcKbW51QL2h+EdwGzT/nK3Iia/qm3N5 +0Z12j/hhzohlf7TQjhzB43Wbxef94JbKacvng8t/hG3+n8UASQzizCSn/oMkXQom +XdQe5JFgJCroU2CfrdFmZfbkkq9mAi80BLUEAGNTUQrg/W39VX1/klGiXiWtpU8g +q/tSGRQHXTwG53qejlUtKI315ZizOhJiniSDx4fZaK2zB9RlZkrnd1Y= +=1LSv +-----END PGP PUBLIC KEY BLOCK----- + +pub 379CE192D401AB61 +sub 0CFE993CDBE1D0A2 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=3eqd +-----END PGP PUBLIC KEY BLOCK----- + +pub 38EE757D69184620 +uid Lasse Collin + +sub 5923A9D358ADF744 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +J/9LIwPdw2wbUtF9MJPpCLqTwOSlnSuT +=AIEA +-----END PGP PUBLIC KEY BLOCK----- + +pub 3A1959EEF8726006 +uid Eclipse Project for JAF + +sub D908A43FB7EC07AC +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +x8rPAFrvagaS2grj0z9tIo3Ki5HXDlWO9Q== +=Azmc +-----END PGP PUBLIC KEY BLOCK----- + +pub 3A387D43964143E3 +uid Tamas Cservenak + +sub 789559E4F4B74D72 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xjMEZR73MhYJKwYBBAHaRw8BAQdAWWaLDXahwBKaC7fKit6NUI9/91uf2a3NkCDR +Vew6byG0JVRhbWFzIENzZXJ2ZW5hayA8dGFtYXNAY3NlcnZlbmFrLm5ldD7OOARl +HvcyEgorBgEEAZdVAQUBAQdAtxXxRe1pPyKD2GHwzqNRbTn/UKZpYvUqwVcMCIOR +Y2cDAQgHwngEGBYKACAWIQSIvjT5S9srU1cETi46OH1DlkFD4wUCZR73MgIbDAAK +CRA6OH1DlkFD44EaAP9DKM1+0jWeb1evljb5xX3CUWj2TtJTgFXyVc1JcVPQYwEA +o6g/+4QJwFAhuN/Q1tG57dpUuhdnw6/XQwhnGTmJcQk= +=YNbL +-----END PGP PUBLIC KEY BLOCK----- + +pub 3D002DBC5EA9615F +sub 91FCCDE555C64A9F +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=T9mF +-----END PGP PUBLIC KEY BLOCK----- + +pub 3D12CA2AC19F3181 +uid Tatu Saloranta (cowtowncoder) + +sub 575D6C921D84AC76 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +96Um/Inf +=Evfn +-----END PGP PUBLIC KEY BLOCK----- + +pub 3EFC46EE83C40224 +sub 2401EE009B11575B +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBFfJfakBCACebHYGckuH6xTvSWARlY1P/PJ+qSsJ/oFOrcG219GERXXO5s3+ +HId4Qm76C4dWKlAJN8P3sXYAlmHrC452dbwiRUUqd0fuoSR7viLyuMvSpcNIzJdh +CvCVJYdk1hH39/VXAEWN9P2WSinUIj4tdO+UfjaL3oeInPtrR7F0yvKFVTkBQSze +jOxbPhTcL17g/ihbuXqMCtm/yjlRR3B2w+cZcEvdRoQHYBoHGw3Gom1Sr5be3sn+ +K/wPFdshWKDvUZDrqFp91icqKAlOcHLYaJskWT8Vb+vF5c1zdAxJhUEYcUZUPr/H +DNSWYkmYbugc4uOx3Qe3i+7x9oCk1GP1RlVdABEBAAHOwE0EV8l9qQEIAK+bhc6S +2RqCFmPcp4Odh+MhjxvR7irpd+KFLvLMOEdSl1mgplB4hY+0jp9y0rXOZFEC8odU +/jv6uqBl7lVSNRPstQxNT6HF8cmzuYtf7N8HNUwPLlkIRgxILnFQDjm/bhgNRAo2 +deSaC7G6JxMdhVLkNUjSj7LyYEvzXmntmacJzhQHj72EMqihnJvppsYogowNe8dM +K66ApwyUowTivpZVx7Chx6pmiHdxroagYUKoSyfsisUO78WpJR/1E2XL6hT3Y9Nd +4/piDIeY4kENF/Sqhsf2SPQONhJ/gGNaONzQk27MC4ko/t7Wjn8avOG+og253ufQ +x/3h9aKhgLaoOvsAEQEAAcLAdgQYAQIACQUCV8l9qQIbDAAhCRA+/Ebug8QCJBYh +BK6x4a7ANcZvo5WJ0T78Ru6DxAIkkGwH/2LBw5jF4ji1Vdj0a996A/GW29tNhM0Y +kL47Gax2G8SLNbPC9GU36p7u88/j0qPaBqPgNnHNOd2dgMemm7FSgKrj8H8hZKyM +Dc3Y6A64Ab4vcNNawHB04r9HJWN9gMHqwHJYMkdnzHC+3nurbvUEcNIaVzwCzVng +5e7s7JpLgdLYZ1nMtRZjfgYmT+DhplGzcr0pLKobvL9TMi3Nob70ysvgtKW2/FJ7 +qdNv533xRVXLO7KHM6RcQMnVUuWFaUToip8Xqc9ss3638UE1DZdfJk0ykthRlTjD +aTBmCu/YU5nL2vvCqIyIQ/Ykapv/YbWaU9b4AV61oVClCiVeY12b0cA= +=uxJp +-----END PGP PUBLIC KEY BLOCK----- + +pub 436902AF59EDF60E +uid Sebastian Sampaoli + +sub D94994D14B55169B +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xjMEY4fp+xYJKwYBBAHaRw8BAQdArb04PVwQKvEhtUEmEu7/aASZivOWgEkZBqX0 +Tovwvq+0J1NlYmFzdGlhbiBTYW1wYW9saSA8c3NhbXBhb2xpQGVxdW8uZGV2Ps44 +BGOH6fsSCisGAQQBl1UBBQEBB0CSPWzZfBjKWyPW+D6RDRLFz5xlO9/30yGD/VhA +EPXybAMBCAfCfQQYFgoAJhYhBB0sfvitoPeUtYx8Y0NpAq9Z7fYOBQJjh+n7AhsM +BQkDwmcAAAoJEENpAq9Z7fYOTMMBAKfZb2ahnfGNBt8Hrbu1j99580a2IaFQddAk +xXZy2unHAPYyfxDLPkbTR7Mm4k8Cva8PCcXotDow4bDLm9rhwVkJwn0EGBYKACYW +IQQdLH74raD3lLWMfGNDaQKvWe32DgUCY4fp+wIbDAUJA8JnAAAKCRBDaQKvWe32 +DkzDAQCn2W9moZ3xjQbfB627tY/fefNGtiGhUHXQJMV2ctrpxwD2Mn8Qyz5G00ez +JuJPAr2vDwnF6LQ6MOGwy5va4cFZCQ== +=VAP1 +-----END PGP PUBLIC KEY BLOCK----- + +pub 44CE7BF2825EA2CD +sub E01173141D06B1BF +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBEzQQMUBCACbwbw7tuTWgwPsDAdQTWGO355jP75oBLHwGgEwV+OCKtxkNXNw +wrJqXst83vmD1dEJyHflQww+d+Olj90IefQGfR+K7O005C2nky7eNGIomxaP52Y/ +90+tmw8qtsI4nsPWPuVj4WdFvlFgUwIZ0SmX4CauVzg0Ris8f0taxg7PH9zEvICs +G/WAXdB9em08WDD6ruhMAvDF4W8Yy7mpGmdWiFD+B9OC006tv+GzYAvUHRFeCnnT +SoKRiBeLejW+t4kpdMnEfC9ILAYBEEjNYvBIyPdPKBwNfy0yjRebsUf0eNmjGTpk +VPlfofjVaUaOZytUOQvntYpocMX+377DGQIdABEBAAHOwE0ETNBAxQEIANp35mwg +ILWWQI36F2SyWFTFF8zupjzqlcF4Vx1Mjs2rQYErv1qs09TJfO+uxPRAva0fKik9 +PFmZ0vLM7UAveZgaknzjTQveOaAJuT5OjudoyptG81ilHO66+e9RnuyV6gSINnt6 +wnq4n8KRPoEmI/nxMhvmBCPw+YmxG3ZcU5Zko4GtjLA4J01nag7jY+LH08qFfPcK +sADmx7EtCRuBcXgQ202kF3o7BjULcLV3R8vni3jp5nljH0vJG5USSY2ZR1bFiLtT +CMO8cm+jIuay25iWil5DaejASIXzbzTtSYg9Skxet29AWjlrt1+zKZsMSLPbKJ6q +hdW5XNU4Qo9ycx0AEQEAAcLAdgQYAQIACQUCTNBAxQIbDAAhCRBEznvygl6izRYh +BMsxkMp4QkOeV/NxLkTOe/KCXqLNnCAH/iA6oYdzQJzXrk9mCDBt/WfIln5p/RCI +pZqyChTOjhrs5IghKYFg8adb9U/gQyxo0SGepYrdO75y6J1brPsxYdGyoty9hQp6 +JkTYiGcw/zlp3ZLXhQ4cTxxuQvB8gZ76si77NinPJzltxFip4u93kOTUf95bT+Gx +p1b2qlJZBEbFY8SwypoCGKt63Se2UYfaKCzBntJg1gs20rvzCmkWqoUd8W+dW7Vc +2aJMMO6TYiiQ8gZGN5Y7n15JH+Si2DTOjHhL4IltF1B0+rah8M2ooIgS2DrLjIz5 +1ztzufVPGTnd8wEzNd8acsZ9tMUujWGlh0Lmx6kBkDVdYtGBnf7n68M= +=u4zk +-----END PGP PUBLIC KEY BLOCK----- + +pub 479D601F3A7B5C1A +uid AJ Alt + +sub 868FF6CCEF26A83C +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBF5CDMYBCADC1/aWU6ZbGZEphRbmjUPNfqh3N5goSnDCou97mmQ9Uq8iBuKS +UXJnGSOHudXK56f+Drx5lGZdLAzveZdqaqb1o3yLFO3PJxwj3Ulhab3O3uTG2eR0 +2Xo7GKjRW13kEfphJrfVIaQq/TiyIG8IQ1dbm9Vuzc5NLDIeC4jxYD2S3hUqCLGQ +BiZAEH9un2cPax+hiT+9MGzdfQwdVrSQ9aEA9mtMhEGsk80XtxXJnd+hw2va6l5s +lSErmH5nMtyKh/n9uo/ap1CfPl98n5VUI8dDtUfWVrqRrkyHgdX+MII0t0nABV5X +rJLneg4dVy5Yw/+FVbLWB1Ta21reyytcIYQXABEBAAG0J0FKIEFsdCA8YWphbHRA +dXNlcnMubm9yZXBseS5naXRodWIuY29tPs7ATQReQgzGAQgA1oQHEM6wP40xPfpa +YBBRAWVoEj/CbAV6BooApSqQkV0cocM9wK905az2FmlKn4WTZyNwiA2eHjHlevsI +jKuHJWhSDVhulcKDi0cD6wTcjctcDWm7M3tvaICbieZQXPH7lju5Ct80kBo5ojdJ +oXGD72kVhSuiF8vOrAOiLOXP8+bpCpY4+LZ4qt3XjSnrkZq+h/vcy9crwuzuFlSL +wMRltOvfirrK/CSQZZtqG2PbT30CWFQ61DLo4DkXeNG1fKpnOaDAMaCedt+pNCAQ +1Vdzy1vT9b85LtOH/CmhumjM3S6x/VWwWZVBXi2xVLFCkm3LlWioSvVs6Na0Vvaq +0WDsbwARAQABwsB2BBgBCAAgFiEEA8EjA4wgqunihshXR51gHzp7XBoFAl5CDMYC +GwwACgkQR51gHzp7XBpoYQgAjnYuxyXaFSbCc2EFWDrBA8+OnlbSgJ3etaIOoLQH ++Czv6+wLYc9snZDBm+IvbzEi3tXfi1TKcRI5ii9wDkti0KcVFrR2tpuXDLWYIF92 +cSC7VyBiyT/aZpm0zX8qP8tjRZvy7mewbnoit1R6ea5UifSLvO0bHqNoswfgv90s +rnuUYMY8tv1sSo4j0f0zre/k4QT8sCTeMDsLsviMIvy1Wls1IPRo1SW1euGGGvCo +bxgke8Dw5QgumudwPqehOZVOkIbuddgcur55ZFFeitMOqdRoXkrGod9v6hYY1Jz8 +W/Y2tzZmWsedFIc4ahuAeZG6cH6Ac8prHrQEz6lSbZ0flg== +=kmhn +-----END PGP PUBLIC KEY BLOCK----- + +pub 4A1BAC7F1E3B89B3 +uid Jan Ouwens + +sub B4F772A2A08563E5 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBE5FdQoBCADPVXY4gMG2TMUl1bFyh0FjfM/T+DtCRNNw24u4wbmTjZMfHnda +H08cT0tSbJSJ928s3a6Dn75YDrrhRbYyhBYBo1rz82swLNLOk5EOSM0VqwqHQEm9 +gmLsdeEtYWDgxmIVfJbMjF77RWwnG8T+fXR0hLAn72i6Y7mm8JT5O33A2xHP9+/Q +g60IHYze/WTAZK678rGvyxwKF4zX2mwYTFKkoL3PaFM/7HbZbLe0HVB8sIF7x+c5 +glEVb9wMApjq83SmhtFM3u/ep0uzcg3c0ks5JXZQ/KtPDYMq0NFp7zGwaFiAF5D1 +BaLWHRFeD6k4c38EPnSQLGBTeVwm+gWHaTjBABEBAAG0IUphbiBPdXdlbnMgPGph +bi5vdXdlbnNAZ21haWwuY29tPs7ATQRORXUKAQgApiVMkMu2H8mMNFoW7qEDTHE4 +S1dkrd+/Ks/59NO1oLKXbYTWsCB4ROu+iqIreq/GOATE73ZecqWk1Krcus+Koghl +Hu+MyvQdZMeYU60QSbCEzGyAO399W3iwPTUW8yvfQcZlXUv5j3BKbRUjjIkc/L+v +2BwJZtNZMcka+lJ/vk1561nAyac1nzKd4JS1I2uhYcGX8OeV+yPTx68yEcD3ocDW +o78Pc1EQstiOGkXSZwiKNj/tmISq9dDETzcpd18RI6hNp4+qG56kcKV2roLXlux4 +5Dco6rn7bdMV40M+KdjrwCy0R2DoIhtbOU4AYZAA9Ha79Yx7qw+yh4QGJwOjdQAR +AQABwsBfBBgBAgAJBQJORXUKAhsMAAoJEEobrH8eO4mzlkoH/3LWnzC3x0zNtd9b +ctpecKu5cgZeaZqQLbur7bAmn5uwzzbkrAag2J94hNoZmdMQMwCXjkLBXUUELsL3 +IUNVFoOQYqq2fl83Y0A8nxamDeyLuZ6pnKWomBkPDW96VkDjCy2QmCViuhJLXqtW +zd9I8VPbqj7vNcT/7GXPjjD5jczjSxMYBy/B5562kRSVyyzhOBd0qMu7LpaHDNyY +vKL37VpPW+9rmD22NnsFqUqYVAD28XLRf5Mn1/zJDV0h03DYpZvCmEGEFwgTDpt/ +jtt4pK5o33gu4TQ6w92QnX1ytOczI2kclynmhKvsr1Qq446LkS9VpX0J62danwmj +r+X9eoY= +=AzXH +-----END PGP PUBLIC KEY BLOCK----- + +pub 530AA5F25C25011F +sub 7DFE92929805E309 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xjMEaP9I0xYJKwYBBAHaRw8BAQdAOUBgNeJu8Xe4w2IE+22aMEiVvJRcY6Gwx8s/ +mfadmLrOOARo/0jTEgorBgEEAZdVAQUBAQdAyPaT/8w88+/NA4o6ovcnWTqveOIh +A3IIVYOlys0o7A8DAQgHwngEGBYKACAWIQT03VnJAUi9xSvrkKRTCqXyXCUBHwUC +aP9I0wIbDAAKCRBTCqXyXCUBHySfAPwPXyiSs7i0yaZetG2b+UgRcTJ9mp2BSshg +HyFHTHouJgD/WCbrAssv7MmNDjfS39AgWpkpr2dbNFIVY5cQlbXHNgo= +=7dAb +-----END PGP PUBLIC KEY BLOCK----- + +pub 55C7E5E701832382 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xo0EVdDLQQEEAJMtYCaTA56YsP5RzQzPvqVTaR2nZ27qRk36blHB9WmXK+NHpGeH +PHgq59mLPVueo2/M5k/fFrCe36jHePP31gYpFtueeYDfsofHwod0WhsHyC7JfG8d +jEnSczTCmOHRZ3ed9ef6SeWUozYCQAX/tAbpoCthe0lTDYhFhkzVCe/FABEBAAE= +=DygE +-----END PGP PUBLIC KEY BLOCK----- + +pub 56028DF552BA32E2 +uid Dokka Release + +sub 7EC19439E4D4C2A0 +sub D89D05374952262B +sub B5681E477AD61C38 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBF+7lwIBDACcXIXAwFDoWvCCWn+OImyyJQvSnnte93Mc1ZJtlArkrjeGU7Mu +5giUH+FOyiXlj7CU4G9RTnAzDgM8XPncWOERgRG2dXtO03Li7iUEX4Z8PCUGsTxP +2VKGuCF4Je1ZPGxeKG49N0L3IIBBxfCzumE37LP3diw7Ups8xJUhZE1ecF/Ow4uE +y6lBOyaJM8VJN65GLOdlbjOTKaFKR6aY7lPcEGyClh+SnMcGTocLf9joBpDI8WZM +NQoQlVtiT6ItvbxjxJmA2hsodm1Ix/xX2xo3hdXH+opmsxlNGSK26hOSMKTMQXXR +m96Slky889SPpT+Rnbp+zPSsWqUJBzTT83DAfH7PJ210bRuzHJZzSox/2iiVbm09 +e8rny09ju/OTA5sVvg0ibNscO2wyGsFjoBTFB4x27Bl+4bloBot2lBO7mRkhOIaT +KxDkKPSw6vQnhA3a7p5nGMo20MgNhP09ui9CwO5Yh3kwnA81clldlMcjQMLy35ch +kSoqW9jnqm2CI5EAEQEAAbQjRG9ra2EgUmVsZWFzZSA8ZG9ra2FAamV0YnJhaW5z +LmNvbT7OwM0EX7uX3QEMAJpgrB0PwR3KYUthxrU+zvZvh5gR3Wumqs75dDrTsgiz +6uDT05YEmflEO8/lvvZQmdQkv6RP1rRjlQsZEYjt6nlzwhlgn3TFIXagUMUfq39f +Dp0Eq1W4Y2hCfk/jOe8YMknYrFI9TGjAfwX0t/bZ2VYOE1FEk17XBNdCc/yvrgTt +F6XSmEOoCWv+2HGfPNo2M1cwX+wJ43TZO2jxHOx//1DGV/gkLoqa3Yqe3ZNwWKVn +wapnqfwlmrNJC42Y26MtsIa4ktsOURpKgAB1uQ7oKtesvaKh3XwW65tgv0kMeixg +vbRtrpQ3hDk+H9iGtIqx+C/1NvzfoIa8fKiby4D8+rXj5S+gjtwVZrY1t059wQSD +JNCWmD4PG/RoowYzdJmwfDxUMptcygJ6yYfn3psTbGF3HV/0jUzqrKDziLjCsKzh +d2Lxca71ItQYbLDjCsI9diiW7U0s+M2PBImvDU8UWwqnprimLCVhsMrRAgaTb4mU +gFF8MoK4QbRCpWOJz4joCQARAQABwsD8BBgBCgAmAhsgFiEExTaii8ifslAnLJ9/ +VgKN9VK6MuIFAmb5wc4FCQsAkPEACgkQVgKN9VK6MuJziwv/dAAYdjuWkkJd2IDa +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 +VHF8XjqIapJdEkbmMXU= +=bl+T +-----END PGP PUBLIC KEY BLOCK----- + +pub 5796E91EE6619C69 +sub 153E7A3C2B4E5118 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +lIJGa06yRs6cyixaMKVlKMuPWIhlGcPfN2KB8A== +=nZ8k +-----END PGP PUBLIC KEY BLOCK----- + +pub 586654072EAD6677 +sub 2E74CACB6918A897 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDiBE1VSkkRBACkCgvt26sMi+0X+EOJDMqdK0Sziy06k47LJf1jOg4tTZ2T9QtP +OZ8fD+va/O5+q8Kna993jzcO5n0Nv+R/K3+MvUqSmdITshCIjBt3cC0n6FWndGyl +jY7rOmzdNnvSkMGE3V2fQ18stkJRleqk3EuWwv+EwpxcYeVfCO+UCJGz5wCgtqnZ +JYdRHcDkDYaIZ4eizpbV4d0D/3cgBdpcbSrwIGtft+lDxacaJrWpT5Jh5P0gLlYu ++6PFz8ZIC4+/aOSi4S4mgZxk8dBL8ZBqLqcW9rc//CYKNtPq33sdf9vxcusMIXvS +PBODjDpoOsTJwy51fgCEL14qnp0v14y9p7ejjN5+GipiNY/JHo9S9kTdVhMYqt6x +6a6MA/40vMejIbZ4q3Ia63jbHPi348fLDq3Gp8Wos7Sh2HnLC+pRdC46qX/5wL4t +Vzj78yW9FdH5yeeE6nQLOBWh7PnSfMt2wYHoarEnkkkycP7WLpRME7qsBYqkNUNa +2EQZSy8DnGiayYDij1YPNUHI9kpK6H/e3puhmgNkzrZj26T85M7BTQRNVUpZEAgA +6xveuDcah4gFC0l2BFR9QsJU0SC5IkwRJC/3GcqAQZ5Bf0i1V90wuu8tt/jJLIgn +VKEFHyTzReTwzoLZcD5zXgBVSu09Qeax47ndNjSfZWNkPmaztM5j9yr4OF5MEvOX +E2wrzmrSNlc4rb5KWK/1pEEiX/zdzWJLmQEzvp/MtZmqyK6pCwtS8S+gKZQjZZLO +EnezizecIce2r6xCRxotqncUwfUg+jMUUlZKUlKwh4TrYDFHhet8azXLpjED0ASG +7/pBYDbRPnmWhX1NPiB4MvLDETrx67aszzrsGXZx9Tr61bhFbRKyDY5ia//5017V +gStGAqbkkCNZHGnQnNzjuwADBQgA6A58Mp77pUtCtVhfBRnziKkEaCn8nCpqM/PF +rxih08fQJ3xt/DbfpBx31Hky7KM1uLgzZEnekuU0ZqwgK3aqWg80moKaJNxUZdd5 +oreFobsO7ptejt5omX6kxdGjPclOt1M8sc6E+A3sR5a2QC/9Bts42myc1zKK6+6d +3UpfUlqgaPvXbGTsisM7jt4DtVz6mXLTyjAiWeO07dcbSjgZuRnHsSCJobzTmNtF +TP1DgUecgTcOK2ajgGsuzLqkbaQnK/RiRIzqkFIWlz8rzlYNXh8TA90BLeGXSuVO +EW7GBIc8fVns6o10OdsAqnzEQqcCZv/eHHXjt9T5WgV3epy518JgBBgRAgAJBQJN +VUpZAhsMACEJEFhmVAcurWZ3FiEERPvbvBoA/kFPHBhzWGZUBy6tZndAQwCgs/qS +u+5vFRvBeGVsg7YSIxOHf8wAoLIHbQ4IMkRivPgSpuxw53Hofe7A +=Y4uG +-----END PGP PUBLIC KEY BLOCK----- + +pub 59A252FB1199D873 +uid Tagir Valeev + +sub 92BD2D0B5B21ABA2 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBFUBG7QBCADRWXf0Fw05qRhM4cRnGKlOW1ecue1DCxHAtFwoqmAXyTCO+tI0 +MEW5SyXUkX6FsWLl6A2y+KgOs669ogzfQ0rnZMEt4HisRp8wpgk3GWR1/9aKYz/c +ymy2N3BP9cz2fJ9+3PpBccUPL+ydFKpcnEnIwiQK+p9JjEWzJBlrdUc/UEJ0R+n/ +5r/+0+BHiTEMvjAF6/SwyntpTWpu7iEzLv/pfdCuhFKa4yn+9Ciwe3wGtSiue+dh +tqKcd4YxED3oAswObBca3CC2HWWsUEH6EmfT1jUdfy1cq4X5x7AZ26oFYfG+odqW +W5dcB+13VkJtJRzQTO/2HKtITJYC65a1jKt3ABEBAAG0GlRhZ2lyIFZhbGVldiA8 +bGFueUBuZ3MucnU+zsBNBFUBG7QBCADbCC7lPXB/xCBC/jqcCGnK/8t/+ixvqJPE +igxyJRenEqbrErFjOi/kRnGYLwg0dEtBBIneOMsvMBTL6GEpbFxyzeEqh/66SyHO +Ag/A3Qi1q2imkWa4baszVkrGMRIKqO59cTuvnLFNe1SQK56ZBjx6AO6KGZWhq3NM +v65ZE1x/viyqofJ4jvQ2qeOqSxa3YL7sim6tQen2gH9iTEcr6stvn7sH1Rk3OwxF +FBbcBoOxZ4gxdM5ft6xRtbnfZB/FFs/hsAsBU+qoVYJYDprSYMNQkmDXg7ELwweG +EyTZzJ3jEnTOgpBHEYS6dvpc/dPsEdCv2vUARNTT7mwGkQdrkEeFABEBAAHCwF8E +GAECAAkFAlUBG7QCGwwACgkQWaJS+xGZ2HNriQgAxxwfwZnOPGHtcZek+p2zRIjA +nZqSG2viTRZxFnLnquMZNMaF11EqQZ4y2lj0K1WSh13TMZpkdwY4bRb7C4Hmo8qS +1JFQ5SjJHRkLbFly9Gm6+HDaDA4l1EcZW14MWfPoSLP6yklirbq6wg9leDFy7EFe +MQK4dXs5CRRAwN7URs444M4OTMJq5i+x+T3Her1dSnutAZrxWL740cE+FMNTg9F5 +brjzmmok4m4TxAnOcy8Qc/fnkUrEW8XHDRMz2CUvF5ffoSMO2OzndfOHDqHscXaC +PyudpB+wOcnxI9pFwmZubWMpcir4BqXM1nWbqFd7tcYPre/0JYIUzKCQANB+Rg== +=k6eU +-----END PGP PUBLIC KEY BLOCK----- + +pub 5A68A2249128E2C6 +uid Tink Developers (Signing key for Maven artifacts) + +sub 4E5C59DBFF7DACF9 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +AIzlSUJwo6lIe4LQfHMIj76b +=k4jh +-----END PGP PUBLIC KEY BLOCK----- + +pub 5B05CCDE140C2876 +sub 9D29AE4A6B50E01F +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +AbkFh1ZqtxZT1P405+r10MA= +=Vy/h +-----END PGP PUBLIC KEY BLOCK----- + +pub 5E975CB00C643DBF +uid Xiaoming Jia + +sub 91A4BA316974A467 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBGHvIbQBDACpPdbz5UIVIWR4cfXyyZEMOG0ayCzJQPsT4eq8XR0o5Y9egfAq +dRXC8paInsaF/iVL8BJY6CNq4B3dUfJwKDcJiCiPbiQgknqF1HDBqQtCb4akW8f4 +Am1Q6qWxTcJBUtVjjlTMoWcCRwFNP1229ICL6XdfM5djqxOuIcCMIHC55YTldrDK +5WWgcXC2NZgUbRn7/8dpaCn8iFAyhYjYKAk1lS8kSxCDvQNU9DtdAWtaCosoWBZ7 +mWWIeWp21erk4jgC145GujHYeqynf7VmdIB80u5OeIV6CwJx2ap8AgrKcOCON3Kp +kYa7fsDGxq6Lbzbc0rzaIfK1wyGo56dMySDK1eZuOY9Es/wzaYfpzx88gn3rrcaw +HwTKBoug+9z09m/rWy1DThW1gruYMUrxjeyhcdXlEeVrv9EdNCbbfKcg6MbrZ1mu +Wc9DIAfSQ40Lv2sKxTyubZXxvoyEhD5LTqSKC0tRt9xKjHWWWSG+cyhXu/2hYmp1 +Dq7XvS1p8NKjD6cAEQEAAbQnWGlhb21pbmcgSmlhIDx4aWFvbWluZy5tLmppYUBn +bWFpbC5jb20+zsDNBGHvIbQBDADNSRxyLYyzfkdxF55RktDmgSKq9tskfR1QcrQg +Wvi96fJekLYL/a2hmJnbfUbqaILnkyUmIf7zONlCFv10U81prG9wxfXUTMuabpp+ +eXUFdbCZMc7R9JGXw5iaRWje+jh/GaeB8xY3cPbtfHkXZ7nWkQaq7b8DutJrAqo3 +9NuprRaaujfU9PXj93tunJExqi2w++fjT1aQ36PaplXRZ2qWiRxAkaIUGxjVgLuO +Y1FWZJjWX2XMypMesUSfUCV9IwlKCc8+55m4jfbtblPQHkbe5zvstxxfHdYQkGng +p1rNWLHHSCT+Tf4Y4aR2J/E3mp6wSBVxgfguS3u9h1mY9fEfe2vSHWkWmYNm4iDA +msPn8KTuccbZlB/cKIFS3AET8ZHwHqHuCb4TvUy8febp4yo5ZRniTVQSf2aA/izu +wmKSmq0bX0hjGRBVdGR2jlD2ND73lkJXU7+3sIwY1ebNVzvl75Y7slmJNHsij24f +keZtcSmRQ7BL7ZpZ3fYiyi1/MxsAEQEAAcLA/AQYAQoAJhYhBNX0a8C4avXcVt9Y +8F6XXLAMZD2/BQJh7yG0AhsMBQkDwmcAAAoJEF6XXLAMZD2/kY4MAKDX+NsPosjH +t95895Z2H8A6JXOEt2F/LqUZ6XOqW1AAeLj+cGMWBDDQYNp/IkizV4iMynmlHlzg +YivT+52xrUvEzOqMRUrDNTPrbkLUjc/Wm+TA2J2ZGUSjZZUmP1sBTqSIa1NCqWl1 +VRWZcmbckCHz5oyoeXKM0EKGnPwOZP0lf8M7f6QDUJY5DS/5RTIp+6BxHhtRi5uZ +BN5QLcamhIADwR9fIlfoFfXEwEAQl5Z2YTnyMf/6/5FWVgAS1PPFqvI9H0HiWPx+ +XSfgJV6K10GwRTtdhT12M4teNFsFUNhngsvO8WCZKsxz4CV+Ai8bSMmWpNsYk72C +bRMoEHMrHuVXKKaKv70eFd+BaGTuLPd8hZgsDuKxU83OpS+LGOYV7Lb3sZNEeqWa +sWMqjeQC/CzEAE+Qfj0RuucswEZGZ2NWvwouGJcTvLAKaUelMr5IuWTkTZYmXww7 +ffu+Fps7qSMnU+mxOl6TkfoQw/cNRpZdMqwI/7119TnFeK5/AORupA== +=GSxN +-----END PGP PUBLIC KEY BLOCK----- + +pub 5F69AD087600B22C +uid Eric Bruneton + +sub 0440006D577EAE4B +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBE7JURcBCADO+9Dc4/JnB+wX+fq+Fr2zUGSPOT6/qjE5kXL4FEbJKsqDSAKG +VnbtRrsIUdmNIFQmz71bBDFhRBbrSrkz927k8eUPhYtxE2NmmWSuKgrjF4qviPQv +m/7SqGx378m/qw4EvpgGUB8EYif98LYdWp5vsU/zx0Ps9auqvetAzJaL9489oE0F +q8FVhve6BMfUUV7zOTCmJnf438YO68upjU0PVBdfFE6Qx4cgCeWbQGy2cooW5azN +iIenhuYU1qikmxMHq2xZzN4uSTWLGDpimPyz+Y1aTSYJ/bgn9gPStbI9sojWo9SS +5gvNK3XqJzMwxwFow86UcIE0vPD2T6ZlBAXRABEBAAG0IUVyaWMgQnJ1bmV0b24g +PGVicnVuZXRvbkBmcmVlLmZyPs7ATQROyVEXAQgA2uNV77VI+ARj1d97b5cY3/er +0Mcc8/Q9ctMY+5YpSYDOQF100QBdOQ8q3IJsfhZeF/iMFlHIUikuSgatb/Ih4lk1 ++irnERPuV2MNoAw3Fvn3/vwl/Jy0ZsQCBSXO54U42TcOXSwNLkYOJaomDiiuo61R +xj7jqijpnydwoFvEi84v6q/Uota3MijGMbzU9QyTX8J9OKMeCSUq0uVuk4ezebjv +/bwA/ax/qQRIrEHDOOB1LJ5JyLacK4+h5J8tMkEmWxEQv7MNokRLgbaePqv+tdf1 +gee4f2fSE3EXKFxjTO2wjLPXCrHSSI5gecsilQn7ZNxH9g2YUJipn9yj3ywMxQAR +AQABwsBfBBgBAgAJBQJOyVEXAhsMAAoJEF9prQh2ALIsrWwH/3s8uN8/gDnbcbTX ++7N/ZfQBXJZ+H9GGikmYRJE1xoOeEt9MOqZyGDTZfGM/qNKeDGfar7pcRQlMK/A4 +Nts5E6d1OX8fBkUBtYanyyjNLlT3yDjO6VaV0SCsgAzNjUZqc4lxS9atN6md5m6l +WLAdHghrXuV6LsiKOS+96htchoCvTvm7mcPI7w146yJRSyCC5+PybG3ult5Y6QAS +kwI3ZWB0u0PKUoqglwWngplu+0Fib2rxQvL32is4YrYaZ+XwoR6u/Bgv0ZvZiypk +17Uk17rDb/JfeLqDn7oW6Hlgi9KOLbRRIg7vwZVo2Ixco7aGxZp5c4zSfaPvn241 +v813ZcA= +=hdCI +-----END PGP PUBLIC KEY BLOCK----- + +pub 61471B8AB3890961 +uid Matthew Nelson <05nelsonm@email.com> + +sub 5EE29DC363BD3731 +sub 1D92EA8934D51311 +sub 867EECC69C59CA29 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=B2i4 +-----END PGP PUBLIC KEY BLOCK----- + +pub 636D10E7975B6E6F +uid ethlo + +sub 708FD22F6805A6DE +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xjMEZtbW6xYJKwYBBAHaRw8BAQdAVRRnooHomzkCvAbwOt9260ltfVmCpI/ibfaL +ObWFsgS0FWV0aGxvIDxkZXZAZXRobG8uY29tPs44BGbW1usSCisGAQQBl1UBBQEB +B0BEfL8PhO+ac4WaOoiQDGTdUuQo3Y9ekfPUfuVRljKfbgMBCAfCfgQYFgoAJhYh +BLGGeaF6Id55/53osGNtEOeXW25vBQJm1tbrAhsMBQkFo5qAAAoJEGNtEOeXW25v +4rcBANF7z5GHPMQNErcZdNqIwA8h9/Xmdpe6Yfy546u5hfe0APwI04BjhEW9L8bN +4rmDTxARN8gDJ41MFXurJAdf4Uo2Cw== +=uK/P +-----END PGP PUBLIC KEY BLOCK----- + +pub 66B50994442D2D40 +uid Square Clippy + +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsFNBGDoYisBEACqUDZnT4h6ma6XIzdC6KR++uDbR2VKdhCuv0Og/sHEKkm6ZbG0 +OFB8tAaQx/WlsoQyf3DlLfUEOGDai875Aqor3fbM+E1hrZbQNfsOySKEE52k7PYe +0qGWlnAzINuQaEuZwNw+pjZqPraMlwc/hwzJB8yFNHCv25pCFohK7KXvFGr5Fc6y +NHBp6pM3pnDQ1kbkloDr32YZY2LdrfdkRqwa9STNMcZtM724aaInValFpVGEHolF +dklo9MIsMI6mVHlxi6UwFSSLltUfTXGYY+rt2Q2sLNnEKzK1GvVhK996vrNWCvpr +cdtbTzGE3WK4f2knhqzlaX99OLmkM1ah+p2EkK7HgWM9oEO7SYpNxKe/F/QfRNRS +4W0aokPsEtfKCD7vQ3cRWQXdqFwvksilv+b6pcSrwfAsaCzVuhB3lcIra4MevJcH +ZEbPrfGMi5/MIVtLayglLHSPoZtjQBhlqo8w3nuADR/aFlIUZ6NGOwaz5yXIGVEs +6E1wiuILRAd7ecJ3Zyr/URHjawfHfKMM2tNCJKl48cScBMY61FJ1EmYzwhDw+at5 +D4pCk75eM5/t6VdYQ1cDWm7J3LGXEANMU5aSZMqgVnb4SQEmRxkW7oq3Z+GIkQQf +Sj4OK6Oi4cUpM7b0m7Cbcsoqb6nD27VKD3J5KTYEq3e+78h0VRjhoi0Z+QARAQAB +tCdTcXVhcmUgQ2xpcHB5IDxvcGVuc291cmNlQHNxdWFyZXVwLmNvbT4= +=Vcde +-----END PGP PUBLIC KEY BLOCK----- + +pub 66D68DAA073BE985 +uid Ceki Gulcu + +sub A1766BE5F812AC2E +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xpMEYvEGpBMFK4EEACMEIwQA6knc/2gtbqDhPh5EzrymR4Hwi1Xf2S0aqMopA1zg +IeZzBgSfL+4fEfpXL4eAzvrk29jIXSizDEOgFpw3PW3Om1gASxub4Jo6EQrRgOdd +OlJl1bajIRC4pAoZafDzhOb+FkjJ61lEJzJ6pQtG0Yi24QWDBfXHkSiQSbZFvcC/ +FTJpZua0GENla2kgR3VsY3UgPGNla2lAcW9zLmNoPs6XBGLxBqQSBSuBBAAjBCME +AdqQOy84O/j7xo1rAaMB3jGHCn42wBJF8nMVZ1oh6WRN8d33JP0ojCpCK9oe3lyx +jZRvBsVkFhOF5lsb72kqR34hALXmZvhwFhzNoQlz4NuDLg6aQjAQEyiS7NqI2SVT +qbGoyIE6yg2ZLuv2svxk1dNlvtqtfOnmoeIZG3pybRRhyuIVAwEKCcK6BBgTCgAg +FiEEYCAKxK52HxYU1sRnZtaNqgc76YUFAmLxBqQCGwwACgkQZtaNqgc76YUkLAIH +aAcCM1niPs/kj3NEmFl3P9ivExlWa6Q45l8qPgitCLO2v932TElX+ux8O+fv0Ax2 +XJezAj+eMV+lYScyvXpmzbwCB36nuPmtsCJ31kYLXhN2WIJWPvPVesreI/GQUq0W +uAngfd6DOtCKYtNKP7xqDu/2bMU23cxGaRj2ToH4RfCClg1B +=j1tx +-----END PGP PUBLIC KEY BLOCK----- + +pub 6A65176A0FB1CD0B +uid Paul King + +sub EA8543C570FAF804 +sub CA890A5FA09CFD80 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=g/bF +-----END PGP PUBLIC KEY BLOCK----- + +pub 6CCC36CC6C69FC17 +uid Gregory Kick + +sub C694465FAACEE66F +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBEtrDFABCADLXGAhjPxdh+naC6XU5kficZYEVAURNRa8MTnaMKr+31v2zcAk +nyqyjihcXGQBCeaNsz2mQkc/MrKdnFNVSwp715JcmcqDJGfR9aIDMUs9PvoNkkqv +IDtx9Tn73piqbplT2xQxd7HjFvaVuy7oXI68gwcyAGDvQZeiFUodcbAsKrVwFT+R +94JU6UODFqE/Ihq15Za8Sc8WhWitm8XTQsCsLeh8abGhkvNEmRLi56fh8/Bz8LHv +8T7r8zkROoQFlUEYaOR2wH5bbipww9+HEg64hrcak2w5smNt0NJUXZ2O1l00u3sF +Qdu54hM9nZ51xxcqvo+tGft9kIr2AfTrJ28ZABEBAAG0HUdyZWdvcnkgS2ljayA8 +Z2FrQGdvb2dsZS5jb20+zsBNBEtrDFABCADqp4wT0RY1+OwMZRfTSYczlBAidkGq +HszDnCkO0U5w4Tog4v085dDFyaKO5LZHkgDJTyF5WyvwtqqzNoU+tjv+RkkctXTx +K02ced5jSFk7E9MI0AoMXogLP/uW6nHmoUTuDxEtCye1UfsIglD99wiCvxGtnOjg +wfezkpctP0WPe41mzNccCrs3hx+R9ECkyZMtQiHYPmr3X3nhwQxKQotwWQOeSYgC +jLhTWJfkOMn/xaGCNGaihYSMCtDkC2PrpyD5V/qj8gek5DyI/bXCv3Hm6Td1XBbz +c1ZHKmF3ccFFPRtBsswtwXuN+6Ms5t0iVsNQ0XdhY8BXoi9H1jo8HH75ABEBAAHC +wF8EGAECAAkFAktrDFACGwwACgkQbMw2zGxp/BdCxAf/TvMMBA7kY4nbH5WU6KTL +Nnaykv0zfxAf5XgRbDqrcO9dvnWEjYTPyyVWXIVkvMtESx0DAHeml/PaKCab5pIo +ybyYQfWLbh8Jhn0+NLnKOwE6m6OrWFv2gNa3zrPRwIy29VFkLxLC/velrIkES8CK +F+gGinmxupi2IpIiio4uCyUaeR1XV6U8UWl/50j0i2mrutle1yiV2lYBb0k8uMZ9 +nqYeHkVQAispv6bDZF20co9Zmn+GtbHat1jHYg0R0p960SrFBlb+x8ROOb0e1u4a +jdAZ2uFczoDRKHEQijvaQxIjpl7Cl1LwyQTq703XSVR19/5ufOR2XWtXa7UoBL90 +bA== +=yfGa +-----END PGP PUBLIC KEY BLOCK----- + +pub 6FD54E1F311B47E9 +uid OWASP CycloneDX + +sub 62C4609FDD6165CA +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=da3D +-----END PGP PUBLIC KEY BLOCK----- + +pub 72475FD306B9CAB7 +uid Daniel Lemire + +sub 1723844CF9A045EC +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBE7+huMBCADW9rfqKBXOqUSLCK5Klag5WqLFxAOddqEM7wTx/42XaIKjDiAW +gmFnV4XBKm/7Z4fwWq7+ku6NDYUjBpI4vcQ2hYJJ4SRWZHT5wWzOmqgznf+/Qwug +P7Ss3EUTRGX3LnhKhKN656XQhM0PutdsHQlUKjvnl2JOaKerEhbHCRxga/U/WWOT +KdobRO+x8v1scsrnUG83J7sTSaja0McmgUhKrhJqrgSk1Tod45SxprxOyp0cgATY +xjHrf2rkafBn7K7aFDe8a73iCJPWS77gxTZCZ72xkcnMLR0m7QI8TzFa4lRjTovA +QcTpr7jwjmyjA1+68peL6VHdVr0cdXm34mTVABEBAAG0IERhbmllbCBMZW1pcmUg +PGxlbWlyZUBnbWFpbC5jb20+zsBNBE7+huMBCADS5gJ9frZF9KUorujSdK5GmZTG +75MXhQLLR3UOczqElryVfudgwHfofBymcuiPPfwSNWpWLeylgCxs49SOrNfh2r2m +Cln7ZO1LwDOoRIfD8xUA+TTc4qbQzo0xt6M7WdEiuhLdxUGnM8s4fRsjwvN7wvA6 ++PGvgaeWIKaP+S27jZaKbVSGqR37Vuj1JkbsZV1V4BXXOb0gyNsT6s/Hcy0owWtZ +kVBIgBanYui9J2uosgRMhHeEJUO05w6ehCoAkr2ktzj0QRDmljmbGjiHEsOcO7ZD +u2JQyI25bVUCk21S1CEdIfZ4xmiV5Rj2Lwxb7LCDEe+umc+W2/7j5RtR7RWFABEB +AAHCwF8EGAECAAkFAk7+huMCGwwACgkQckdf0wa5yrcgCAgAh0VKQHRwwov6YDV2 +1H0/h0vv934brwNWxIPz46WVa0xI+fmA0wYXg+1OTQBy7rqJHWoK0I3M6qjZvvaK +o77yUUtdye16gf/SxKPUwXMyTg98ojOvq7orvDF0ktE59stzayjUs3vrR7xDh/mx +pwiCN602Jk27vCCTdg0AHbu/CsK+Cu1rkqlqa9nQ0O0No0IiGJdDK51/ZluGBhhB +73xQ+jqgAf/g1qabG0Zyv2N+5tw+Alp9sqDsfyA119B6q3xKoaJqo+9h+zQ0vpZr +NMuph0EDebGxR9VMiO2F98dacAm/Qr8QTyRj4424rkCSK2VbZtvalewylY514xg2 +lV/Shw== +=8Yuo +-----END PGP PUBLIC KEY BLOCK----- + +pub 7457CA33C3CE9E15 +sub ABE9F3126BB741C1 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBFIXyRQBCADe285y3Pu7KzoKyP6wqeNXtvvuwMatAmPm5x/i+S8MlryqzsYa +x6twUmXV1yKjjtGrO+9fHvTOWBfSSP+fP9KTaTQYSasoJq2Mw4cQDy1i0zrxNZUw +N4/BiyjQA25sdfaOolhO0sFlZuTZpYy5wG72KkA1ygNq0L+8aBKhEF6zDU61YzCC +AxjcgTftgTeeoqkJtYa06lNz3jmJDN+zUQignfRa3ymoGtFHTzoXR9maE8RWDty4 +y+DY+8ibdGgSgKPZ0byTCDyNojgU1YTlADa/1/NY1ShYg617O1xicLNo0JEJlf2U +Tu4Ymql36+xSkYSISU97Q6Utgq27XMuZvDUDABEBAAHOwE0EUhfJFAEIAN9NHRd2 +bYP/3CDi+n1ilSChld0NR3DUBgS/AdqQ7IoAUfj7skyI/WyaMdV4uy6vRh5YgNg2 +g01nd0LLZR8Gf2Ck+D6F88CdZaTxlkcxHV/dXMZ8yBO+0D6yFRZEL7Imsv8Ig4QX +OVwfuiXEPk/Ef5Dy9SdAVhcoErTGGR6BOGVVvexGtBwefsjMaOG0khkRbWIQ32Wx +fUFuAv5XBQ0ckLrlKvYWUYhOlXg27GtFKH2EBBF0Z5ZWu7gaBFwSV0oLp9EWcD+C ++WEwUSfBdqfRJtyXvgf4kZdwdQ5caM8P2/Sdncl2l/LU1At2Smc+plr6zhIhDlLh +lrzKGa16oARSBdUAEQEAAcLBlQQYAQoACQUCUhfJFAIbLgFACRB0V8ozw86eFcBd +IAQZAQoABgUCUhfJFAAKCRCr6fMSa7dBwURMCADHrqwRNHkbG1QsXJr9oUK6KVkL +sPhcngIhxRLlqe89omg9G7eGNauzs2PKsB3txotCFc7ROVNv/TAuSDYzkPos8G46 +p3bGesjfJb24zc6GMT4RGIJoh1oNG1IciafIIHjp2ZJHRmEDwmvZG24OHJ+mlHLj +aedtqlWu+zwwhH2VZrI/U3gW/x4imbk9UyyzciEIxrAc+fc19xl5PkUVcSDVC0cA +qGpeZz8+SxFaf3Rr0aGnSbeuHRjNupmoxkQOAey1ztmdWiCPf5RFfmFD+fENh+/x +qYiGorYpcIN7UAsMkvD5UHc5ZG2tTD41jM99w9Lm/xHJ9ks8gNwZESwIzr6AFiEE +Vu07SEParMed5VVVdFfKM8POnhUEogf/W6Wx+UjbXqo/Pmju5TGVObw19PIBN8JS +5kQMxgY6MNA+pBryEV5uqNZZgFSrZKVooPbEx4S4RdX4zmTzwLHpPae9plYyysPu +4aP61OwVooEbE/mnY5IRY5MRMoq/oLHu9wxNCaupIWjjtsUqU1Kz5pUzjxSdB4W1 +MPI6HBrmO+2q2y3h5AfAdZD30ErRMTKY3HS8XSc+NYDvoJGt+ik7Bcp3EmCgqrgy +vET3ai3kjzYBkE96JZUn7rAQJ4r8agEhdnmMRSqtCabMhuhYDkbk8wnx6DMsk1Em +NNYeMJatROfA1ypoPj+pnEiVbgu3S68aXHqko4lDTm771f8HOhVavA== +=f2bL +-----END PGP PUBLIC KEY BLOCK----- + +pub 7721F63BD38B4796 +uid Google Inc. (Linux Packages Signing Authority) + +sub 1D09C015006FEAB8 +sub FD533C07C264648F +sub 32EE5355A6BC6E42 +sub 1397BC53640DB551 +sub 6494C6D6997C215E +sub 78BD65473CB3BD13 +sub 4EB27DB2A3B88B8B +sub E88979FB9B30ACF2 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +Bk/RkV+Tl/Lk4g== +=xlSM +-----END PGP PUBLIC KEY BLOCK----- + +pub 7905DE25C78AD456 +uid Protobuf Release + +sub DBC5123E2E98FEFE +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBGSsZCsBDADJZoPoHGJNAB3sn/kFQ3zlj+vZ7OY5aWoH2nL3tHQYZvN/pJRs +8wu4Cw1ApatqLIaur6S6LR+s4xB7HxnMvpiF3NMwr6ZeZBUUTGEJbRgFhY9TqZam +CZJ/xPz/FevPhZn3/McqDGbjEx+G7hciUl0EwIOhanAQQvVYaWxDL+Pesqqh23U7 +Cex2NcotieICt7dWJ7SAM3TOSLP9OQd4scRvYLWqv6/vu/nQ68RwqaonR2QzxhUY +Uul7vR3iNRXtbnS31qIgCYWAoX6w0xHf6KUeIPWV21ZIUu5cg6kQr/sPt/OQuGS2 +nKk+InYtopDi6d7AUh8WI2TP7qAMIoRkhAeDEQ99DiopwFNPA/7M4g99AQfFSmp3 +acPCdeXXAZeDAqoFGFKTlqzg3FLWpGkubI/iXyHkpQfOXv4MtYuPGVNheBXGcWbf +XPjbkFYjkGIN2Wx4i7yf43hMCk6ArhswfgCcgoORI+DCVdm7ORID1PjIU2Z71EA2 +qDdFwdoOdEV42YUAEQEAAbQsUHJvdG9idWYgUmVsZWFzZSA8cHJvdG9idWZAZ29v +Z2xlZ3JvdXBzLmNvbT7OwM0EZKxkKwEMAK4LeTj1dr8F9E98Up6y4AKHY0Zbeb5v +c/TzsJX6UCudzygYTbQnEcrPIcJ5TJV5leniAlxnqUz/qJxmpBtGCNH63c9+iJNh +VqJEZh9dbupqQn+mqtBvsPABbHU+C46TLebmOK4R99zgtxVlSYabJubuG2Mqnq96 +mutBUWKI3iY5j0JAMLY1DJesAGwAWP8gvUZHhd4LJN3iikNSTWyUE0Hnwm2VKFq4 +cxI/6qaCpztfuSD1y0JplSfmKRd+ecLSqhDvlMZkwigUpjCvF7iSaPvpxWdkFabS +frMeIjwbGU/fLV8ilwtPPb57X6Nrk9NIUdVa6ZbxiuIErIcp3JfgfUfy7wxcI/Uj +Mq1I50NOwizLVprZbmKv1P88bACmdon612pnDhhs84phJmA7fzQ/jAqF1JQ4Crdz +L+6g56Kkx1VlN3dSmPjuycjTzykuNwZ/Fi0Lj9Czg4LVp6peSsPWS+lp9h9tOSzt +lQev+GXiQKZTYt8JxvBPOkm0hd5M30BDbwARAQABwsD8BBgBCgAmFiEEGlXwka0o +wH+DH6RNeQXeJceK1FYFAmSsZCsCGwwFCQPCZwAACgkQeQXeJceK1FbX9wwAmLBK +Q8JljEwk0KqYxawrusWXwaH+1I83urf/WsOJYEkKoiQObsFGTuaolyln6ZHyF+gt +uKeWtlbvG6aXqv9XXcsVQG7NMGdEAy6DTNj77uBAXMWTxVpD09iVeepvWSiz7r7M +gzJfluNgGDOGKpkxxIjS8NnOAsK9uquyvBQa97I+YniarTkpnVWpgSR/7V3HHf6Q +2aCKL3ihdK2uIS4dIrFi+mVCt2zDad8U8N7S2Gv2VO/vBF+hIFCV788hLH9HeX3f +70E99X57hrVCh0MeColOIV1zwK8GLeV7bpr6x11x5cjiv27xky95WteyH5w9w/Xq +Tu0NQ5YyKX/0PUYVX3mLs59H7Wys6ANygWJs59JT4KSwb3pIEV7gWSwp3mWkstlF +m4Tq/d+gVF64ItrHylZg0WpHPv1s+dH6/tWcsBnkgR/OS33PkijQgvMW4imQNRxg +ymOZIduHXX1X+KzlRZTXvv4tSFnIQ0mWY1ySiOJQJS2WABVwFpFc8rECm6eN +=gmVv +-----END PGP PUBLIC KEY BLOCK----- + +pub 793FD5751A0F0780 +sub 59EF9FBBED4216F5 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xjMEZzuZjRYJKwYBBAHaRw8BAQdA/FAoxAeXKY80R8UZ35NNr06SMFqC3Pv/LtP2 +sZS+7N7OOARnO5mNEgorBgEEAZdVAQUBAQdAcU+jcl01TAmiI2RmtjPv4c/sW4nE +TaR06bnQoHFL3HMDAQgHwn4EGBYKACYWIQQdIX+Ede7p8Zq43Wt5P9V1Gg8HgAUC +ZzuZjQIbDAUJEswDAAAKCRB5P9V1Gg8HgJgoAP9sdWX3itgWylKDw70h5G3ZLNSt +zqKyEx/3NURzCWXtUwD/dkcAz5qsu2LGQynLaRXgNI4ZVgTb2zUzZM0TbshIzAo= +=BMAA +-----END PGP PUBLIC KEY BLOCK----- + +pub 7A8860944FAD5F62 +sub C189C86B813330C4 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsFNBEvxja8BEADAzZOup1X0B12zJsNsDvXVIwmM6bB+uhEsUHoFTvmsEVwRoZtn +i7Q0WSFoY+LDxbvC4Bg1+urCrUrstRJYRyF/pMqPYq/HokRlPjtrli/i3mUSd0zN +PGC5+qXvAlOck3GK8Hv05PsW32SlSczZw6PSDKh0natuM3hnb+vt+w2MXadXoSwU +EV6GtSZpj19vRzAwG/Zv+ZUDCBXVQG13mG7nr6+Q9+E0hJf8i/XZBcvTuWPy5niY +kzWDetDqNboFgCvBXYUw6dJZTS3tHhrXXp+W6hoSZFzYnRMG+xg0ls1z1ejUZkwO +mWPL7fr0Z/svSrOfyRxavKx1viKobEdnLwsdHIVK7TGIe5fQzR7PQgBgpMCueoMQ +NoXkA6GqPTuwS3pgNz2k/K+Bz3ICT9l09SHXzuGcB4GObF7fPDT/UK73Mo3sM0M1 +u68Q51i3fG92Owgy4Z/YXN/IgnAUrCb+EkLYIscSHby1voyvj2a/nIXajmldHqNX +9yPJhkIAij95VcsD4OUXonFbfqHuV7WqXBv4AhR/z+BndUbMbrlkn+r8dfL77rRY +63EGV3k8A6IB/WJScGveJsNRGCZLReff+UyvRkRy0jVVI0/G32ge13PbpPLGHoRx +LXiBSZ6Nuat8R4PS3ry8HKzFx6r2+VO082ptyLjl7e3yQzdVNshpxYxQZwARAQAB +zsFNBEvxja8BEADfuM4j+dpNgMDDXGemxTG2HkQYiZNro/ytH+WOBZ962EgKHWt8 +RKuHD+69fHb4bDjHKFF8yVv9+okei0qK13SWc/+uRUVyLmn1xPX9cgTvjChfsnRG +JlioFZ3XxdQJ3vH8h/Mqb0yqxAgjoWYQIqIeAlE+7IwNYZy+LsuDD8OUUSbCN3zN +Q9E42Mo1IDwiMgHl6IQEWvYqjuICiu6nEA42bWuMQJuc7H7UxvzyD/Wuwdiy2gxA +HAtQMh0i9N2YcE0ZWd2ovpzSe3Dizx95pxUUsaQG7wpu3U+qvxCZjP+/XVNhkDvq +ROuXGw7B/5g/0OMORgR/nOpodXf1TFpSEU3uPLTwwxYPow2CoQ2X9787ojJODrZE +nQ9YdYU1ySX2Rqse7QHOu5Yf/Mnx4G3mNTLAFHYlzp/0sjaSRRzqOooKw9hUpqNY +kvh88h6QQLckdH9TKIHqJk9UiENIEv37XJaVsr1WSAvPeHusQoMS8k/A/1knreLV +OFh9AoUKG+2gjYs6VUR4f1epLEWLBvsBBwGwbXbwwOIb/0blrjp3h8yp50Tvy+T0 +hco9fQW1O1+50aztQCfVBIQ++/NVoQX7d5z2K6TEcRfIFoIMbANSmB/ZX2auSNIa +U31hVn4cuEOyENnLYH3XCELaGhce6lMEACD1J1m2i0Ttfr13NeCtppsGMwARAQAB +wsF2BBgBAgAJBQJL8Y2vAhsMACEJEHqIYJRPrV9iFiEE1vG8eGB4COyOn2lDeohg +lE+tX2Ih+Q/+OTpCunloKhRNiKfMe3hZLiaCeKkcc2c+jZI/9Y5VqJ92qbWeShW6 +nJ4/4wNdAUggyTwAaMV4qncYC360IzgaUEYvlpnpD0ES0xvIVzl25lJVLisJDS+w +g/hlL3fsIqlOBiGWYREW0T6zRwm4LAA26n3CPgnF6Esput1CT78aeOjldEaYYecn +2zycZxJJ/EgJc/MkooYZpkKzdyzlKwcVoEdSjI0sXMzgh6Xev81aAE0zG9eM5Ev0 +a4+sEygp9pCAN5JIemtWaVzvSezsoBcWmeveaKWVKzU2WwWF30Jh7J5vm08R7wka +/Arq20zEcHGbS26MlJ44ZQNZU6QcQcFrPkYjgD7x+a9InzLPzgsRW6PbOBgm55zG +iJOCmCiKlMhePzDOMfYo+AekglJZvWYt6AC+iDu0EvsElg0EBtoo0ny3azDAjJwI +5/nmuMQF80Pd7QeUpqeL0XZl608dHppdyxjKXvqtVe6UrGJdifmWwAOqLb7rcHmI +yjnWTNhGdnkbPsxHGrl7hsoSOgxSxgmMO+Vl74ueArTC1bD6JhB9j8KLDkx57Zal +DrxVxHJIMso7y7QkemJxib8JkfFsaOFye3nvehO6ohGnt42hqvBZWke2E/7xC8ds ++UM/HfWdrkQve6YiDHdF2x8pWC+ok+JbFn916yL/54nwMp3l9/9ITv8= +=e092 +-----END PGP PUBLIC KEY BLOCK----- + +pub 7C25280EAE63EBE5 +uid Oleg Kalnichevski + +sub 926DFB2EDB329089 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDiBEPonucRBACtbhYckAoyz1tuSXYX4XiqGa5390gIMcxe2hJ+Ncx9o3zX09Im +f8PW27BnMrz7EIydgB2wphhjfK4vkNNtm5ZDWH/zJStsk1Fe7lNuuxs8XorX1+8D +bhhFEuc2B85vNf2o9Y4V5GFwbD+tFNy4u24n7zg6/VgE2WDvYJ8JRqCEkwCggyLj +ba0lsZ2XtSINh/W8ok+9f0sD/A8WhqBfDTEBuG9gnuCYXM0j7XBBPdPS+FXmmfea +zyP+URKRprLCdt0ThZAMllIxZJrkbv7aeXVpM6KSZ/XvvaFQ/gha4o4iJFvpoKt1 +Er2j4Tz/STKztHGsMt6pqfrMNPWovu4tLuLZQmojtbIk+IwmcYxMy99owH8oV1WC +U4HeA/9MlUxzmlmrQF7VLqFTGEEqQaEJqz95wNPj/t1DmI97hshPzXLD4zwKwa9m +qZJPStRHM0a6xW2dztF12aXhrmYg1gIGNnsHtq+t8ZhfINZUurSWn0m65WT5notA +15s6hwyDACHWWOgFQ9jmWuGDh0ZpiaBe7BxeTV+MsswY81sOn7QgT2xlZyBLYWxu +aWNoZXZza2kgPG9sZWdAdXJhbC5ydT7OwU0EQ+ifBxAIALIr1cwH89+EBPkuFk1w +rpJ5mIkhBJ8k8JPWsG+dr62JYaENrdhIUQpXP7UekG4TTIWjMT7dTmHrrTr2TeKt +teiBNksGcyAw03IWAKT/26wXSA3+C/1xlhSKn7Qkp/r529CwM2DVbjV7TcEZ4tqv +32d4NcJ4lTGydjKyTbdlYS6z/Sv4qRc+yQiYrbTJ4Jpgxe5hCoZ66gUTi7mxbFHz +Zs9akQuj4hMMsggnRvuJvHyIksqp2twjJGa/1GlX8bhmR1jPB2BpokI2n6ni6qPG +km1nq8w2LRdvr+IQALfXg8HYGMvKDcVxL8O0PhuZYsfuklDGFejSizVGwWH35vcI +1bsAAwUH/A2wO0ac4mVi7+wx40d9QAOZ5xhHrndHQkTvK8H0DZ2kL7iavLIkZlN7 +jwC9jcA4DGmtHE9cbe6eP/P0VNDfaLVap85CzFE7qqv8LUK0LdDlrBCUcsXplhcI +i/WaVOHk4OPXHzkF44Nqt328fQ9V6+gbvz5+1A1PK/Rmw0rRie0d2dJJIbQhK1px +jV27qpVXfLhLMnjNh2KTO+gZh//LzION5TicsoeiHdtGU59x4Fs0SOUGgydSWAYT +LlQoW0z1AnDKMfqoIh+CrUSAI7fpt8NAMY7KqNxBT8HlhqUX6jMolHBEreF/2fVd +jX+NTR/p0O+L90b6T+xFin/RXxLRaa/CRgQYEQIABgUCQ+ifBwAKCRB8JSgOrmPr +5Te9AJ9XRqxecT5oej+N1PfmcCTLZNfGiQCfdH3+RPBbKSQofz2bGx7/niTd9qg= +=dFPC +-----END PGP PUBLIC KEY BLOCK----- + +pub 7C30F7B1329DBA87 +uid Ktor Release + +sub 72FF58594F983302 +sub 3967D4EDA591B991 +sub 0588BC69A286FF16 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDNBF+TCd4BDACbIA94MfIWL0SpvZwBddXgx36Lp9GYOWNgGoQCWSvk9vaMrLaI +rEll0xnoP98CfBQYrVSAmHDMhSLBCjNB3V1Sdz8GRdOG7HUffF7Cqwbm3Fxo3H/h ++Tsrodv23NuvKsDpgglUL6nJy5e/FO8y9dcxLXRRVdPFDhJubi08SiUJy9FQbnfA +yb2LuTzXtjDmjEsMZpdpQUlQkk0xNDkrrq+2miwxemVd35cnVQCFP0K7c4T0ksGg +Rf9A2r45DBbPfvwTL+ZbrGtCssUpCneWhPl79UsMxeY+vJjEggqqqRqbHRn6nOQd +3gKSaEqdALZURPzvkKxLUeUUtMk/tkFdsNe/ea7edk6G3MI4dbUY7p0XLS54S9cB +1JUAHNEFtuJQKGWNuwWO58Yun1EBtOdUEvnIIoQ+CIN/XeKrnEIXE3LSblB8BR3H +bqX54BMe9AzsmDQtc5pUOm2pfvCoiv8xFXQznBg24dGqo2A/jMoUnGj6oRj7k8mt +i9AdPLigldr0S0sAEQEAAbQhS3RvciBSZWxlYXNlIDxrdG9yQGpldGJyYWlucy5j +b20+zsDNBF+TCpABDADRarOqvERlpMCJjNXGZpK5sV7Umndyu1rwVOfEBhINkRX1 +vzzFJFciIfWEZ2c+vSNnXZC+vFuAYtwnHqTWwyodHU+/jwHeEWQ9WcD2buSwJvps +kSei7ZMSWx7zAGWM4ae0FmjJrVHEQhM1CgeDwrxIzJqoOhrC26IorT7bGB5M2Z2n +NStGz9fen71jNeyo0fHvvy9xkcEWsfDd9A5V0odRb5y1yKiHH4Puz+o6Gys43/PQ +Gzf2NBx1sjzQjmJrrufvMIzRWrJwySYJQZkr/qdJyqbKZgbA/BWTmpN9POranNd0 +YO2/lbD7eiDkBflNGnWcb305VVzyZSD1kXXeLAc+y4cQugj+FkA/9Tv2c75sIhXP +QAlZAG3ldj8WSiAlyyVuuWZh3eyxxH8J9LKDXJpBqvNvzucso1PQS2HzKhT98GxX +45LRTsZo6yM5XAFgqw42KqTmcOy97mzluVCos090d25zYwCYsFoSaIX06wYz1GuS +sW/JHXyUwsG6BWScgqMAEQEAAcLA/AQYAQoAJgIbIBYhBDlMtDbFaRb8Ae6kp3ww +97EynbqHBQJm+cFdBQkLKR3NAAoJEHww97EynbqHNPcL/0LeMIWEx9SGbxuBBtIl +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 +s/VObsFtKMUFPmME +=10eM +-----END PGP PUBLIC KEY BLOCK----- + +pub 7C7D8456294423BA +sub 9842FE565AA0601E +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsDiBEvsZw4RBADH20nX+H1xvMBYmXRj1Aae4dRr6Y6qI7QRWHO6Z7/dxr9bk/NN +Yjq5KsVOQxZzloVdtqx75rznT7fZq98g7Nq9IeEtB6k4tnh6XQLhljJMk0a3mzdt +q3VzdxeVbwCaPJ0zixv8XPTAH6MpRJUvP9XjzxwaYHrjwcQ1LslW4TrIzwCgi5rf +jChLCyKcaL05gqUjl4lmefED/iqOwYZw5pJ8+X+OHUViiOB43wsJt1brAhPj4KgB +ODStcE6WlHFKi7YzcYNLzYMebSGYn6bj65b3qNf6rybWD1hGUFK4122Q7+HCH9Ic +J+rr8HwjGFo/yxI0/mkyaF0BthXYPy4WtdsdTM2kgx8Zr3Q2rSt1jBPuV3q8d27z +FZMiA/9cWPkRx0RfAJmBPKmKkbBkEtBbNau3G7MY1OEAkEkRnzmnyyjr5IP84A7K +RdjTCvkbiQrOQH00Ki4sHIg+9Xv1gDg1XLkFDzRARKA1TxjL0OeS4RWF3iia7Swk +MOnTdhR50pjb18W8kB4mEMZY7duP4nwDfQwHMwbFZGHrjImaus7BTQRL7GcQEAgA +thrbFDDbIGq97dbX1ZEwNGpjILdeumxZzaCc5PgldHGA86TL2V9iPpONJtv7/Csr +1c/rvH0IA/BFu+0Kde9UP+z9JycmfJpIsd2Qtxfhn9R4+Td0VtN9NNkpt1+JcThb +zyQq4UU82uBKwSG/wzubOI4Fn+7ypIuNgDkqK0STIL3ucKF/RV77PsOBeLHkCGKI +VhUUfY1D51BWZKKB1efaVK7PLdnsi64lAhbaexA43dwUUtVwQOvDzJVtkycV2IMw +XwyHonlK+4vbcwIH2BAU46LIZy+VFaI+lNNmR9+xeWH49oJYnMWIXif4RzvO1ag2 +KKwlG2sacuVTtpWVqKayBwADBQf7BGl4POKtluAbX3a/KGnX1YyibPT2e4pdOxqb +KTwuRc7ILIK/LVcejVuLd+eOF1QmjToI7Y3qjlzvtaxSemfkxbIQpdus0SyHEEAG +H/qjNuTs8ZwEl9tPPrKaJHbJ9kQrlDPwkufA0NDAXTE76UhcHPY0DeykAKEw2rav +EqI7Y0nXR2WSmRLFChmQ+aNTm/9UGI/IoRdS/dgo1eaLD/7RLFeL5tGDe+2nj+B6 +j+52nZEmaRCWWU7O9jYL1cQu5WTurtneCLtIqehLsjuVz9ihBysYuUcTOV/6W0AI +clQoMEiFR9Kyv0owDk6LtX2DgibJuAvvLlnajsiGm5nyVh3G1MJgBBgRAgAJBQJL +7GcQAhsMACEJEHx9hFYpRCO6FiEE6FrtFVAhr4psa3pKfH2EVilEI7qJdQCdFwvD +XuUGF79ZwoVGTMRNLFolyMgAnj8/U006f95Jwt3HbuyN7urewEQ3 +=xEOq +-----END PGP PUBLIC KEY BLOCK----- + +pub 7EB97D110DFADD60 +uid Niall Gallagher (www.npgall.com) + +sub DC0B7E986BD7398F +-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBE9/RAsBCADI/pVIFcoLmbq4LCKkqeN4i5xgGKsuQsDAf/ndFkILDUA2FaPN +7cI3EvZacWnWUA0QkkKNKpajU2OjjQlu4IyBosJht3VMtD0BJ2nL8eIDvwO6L8TS +2RRGMnMaDUc91NnoxKs/7VlQ2ySk6Cm6lH3t8KVkwaJdU59lAH1ey9UKhYyvRQuT +htenl2R63lyyDe1ZLMAlmQXi4RcCWOO+L1emChNv0q0Fsir+7go9ZNYUi6pmIEva +jKXM8bo/VtRIHrS73DsH7BVVCURYoBWexZWlRdb86KSE993dRXLvFPy5JzlRM+eu +mUY3CMKxx3nLaDN5qepf1nGzMW88xjq4z4rhABEBAAG0M05pYWxsIEdhbGxhZ2hl +ciAod3d3Lm5wZ2FsbC5jb20pIDxuaWFsbEBucGdhbGwuY29tPs7ATQRPf0QLAQgA +68HLImPvBSPnMtjUHczE+gccsVWzLEsjVYSBcOUi1j67KQHbTPcHAqzYJl19t4FA +N/yU1oOjuu/4GKVni27y8NGSavzY5elTZ22lqUqgqT6DjoOG2BTLHuOiNRIMqBmD +Gy41mEq62C9I107pqJnnbARmde4646kDiaf2vkF1BsnBx0Dp93re2eJq4rkAf803 +fDvA8iyk5uDFiGg3f70JAu7ZCAKczglD0WUjIiO5Jxncz2sWiO2OuVgdsTuZf+9T +0aODKua60Z7CLn4ZK4ZpdibbOEp66XLeaGuy5HPInTTsr4UnT2kvor/AmmPKOryp +9oBFnPvf5+wREwlQN2h/PwARAQABwsGEBBgBAgAPBQJPf0QLAhsuBQkHhh+AASkJ +EH65fREN+t1gwF0gBBkBAgAGBQJPf0QLAAoJENwLfphr1zmPJtcH/RJ5ba5m0Obq +BGbcJpJwhEjpB6tCOufdzvvJGAMMAuH0Vs5kXrASIJPyVgJ2ab4txg6U3DKIfxnE +IGjfdH9okl/oHRYrI/EDMN0PnIkE1JidhVOEOj3UWaoLUS8vvobKq0XP8B6J+P4q +NA5L3cPlBBtH7yqzVNavi6ljJcsJH3g7L5vJDQyw+xxfOvQq66y4lcO8ptAqB+nw +oHfSsfRKQQgT+Xlp4lG+acf+Kc0bLjWWUnBRgJfkhbGPVYHQ/QfnxbuLvlqohive +HEV+d/PxCwUHq4EtLC9d8V3ADCZgb3v9YE03abItwg7tnQBd/LuJ4qdOEbjAWI4c +crfZTmD74BlsewgAsOy6q3qjZwBwDjUHc4+Mq9ZwaehZ4Lau9EjyrOPZw1kSCWc7 +rcxlTbPa+2xUrv382Zc7KovIMmEos4RJ1gaFfXvreGKzCOrlsA4knXcwp9nywjZH +2Hd+p7l+/EB2YA99rMglXl/5Nfs5k2I1bTqfOb2eOXde5p8Akh1exvuIyJmRf/pR +eRA/8dXKCE/qG+KZkJOh8H9gD/8nX8n/5Qfm6Ff0iFF3mdBLM9QdBa36/qowBYQz +u25YFH0NKiziF0Y2r0pd4NQhoAirGdamVPfWKzvHhZ8XWhklr1Ve+3X9NqZIOcXQ +x1rrD3BL6LwVJtHcfQxuT3IRDnjnYXZAUOVAlQ== +=y9wj +-----END PGP PUBLIC KEY BLOCK----- + +pub 893A028475557671 +uid Gradle Inc. +uid Gradle Inc. + +sub 5E9AEEBA28836032 +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +o7LuZ+m3WEHiASo= +=YVmJ +-----END PGP PUBLIC KEY BLOCK----- diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml new file mode 100644 index 0000000..508ebe3 --- /dev/null +++ b/gradle/verification-metadata.xml @@ -0,0 +1,422 @@ + + + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c71d502bd022215189874f68199f2b85e9c189f9 Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Thu, 21 May 2026 18:37:37 -0700 Subject: [PATCH 6/6] chore: expand testing a little bit more Just pushing us over 90% coverage. --- protocol/build.gradle.kts | 2 + .../connectbot/sshlib/client/FakeSshServer.kt | 11 +- .../sshlib/client/SshConnectionFlowTest.kt | 113 ++++++++++++++++++ 3 files changed, 124 insertions(+), 2 deletions(-) diff --git a/protocol/build.gradle.kts b/protocol/build.gradle.kts index 73e2ff0..73e5253 100644 --- a/protocol/build.gradle.kts +++ b/protocol/build.gradle.kts @@ -78,6 +78,8 @@ dependencies { api(libs.kaitai.runtime) implementation(kotlin("stdlib")) testImplementation(kotlin("test")) + testImplementation(libs.junit.jupiter.api) + testRuntimeOnly(libs.junit.jupiter.engine) kaitaiCompiler(libs.kaitai.compiler) } diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/FakeSshServer.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/FakeSshServer.kt index 3c3a0b7..8c84c6c 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/FakeSshServer.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/FakeSshServer.kt @@ -109,8 +109,14 @@ class FakeSshServer( private val receivedChannelOpenConfirmations = Channel(Channel.UNLIMITED) private val receivedChannelOpenFailures = Channel(Channel.UNLIMITED) - fun start() { - scope.launch(coroutineContext) { serve() } + fun start(ignoreTransportErrors: Boolean = false) { + scope.launch(coroutineContext) { + if (ignoreTransportErrors) { + runCatching { serve() } + } else { + serve() + } + } } /** @@ -741,6 +747,7 @@ class FakeSshServer( } suspend fun sendChannelWindowAdjust(recipientChannel: Int, bytesToAdd: Long) { + require(bytesToAdd in 0..0xFFFF_FFFFL) { "bytesToAdd must fit SSH uint32" } val payload = ByteBuffer.allocate(8) .putInt(recipientChannel) .putInt(bytesToAdd.toInt()) diff --git a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/SshConnectionFlowTest.kt b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/SshConnectionFlowTest.kt index 05c57e5..1b67a6f 100644 --- a/sshlib/src/test/kotlin/org/connectbot/sshlib/client/SshConnectionFlowTest.kt +++ b/sshlib/src/test/kotlin/org/connectbot/sshlib/client/SshConnectionFlowTest.kt @@ -60,6 +60,50 @@ class SshConnectionFlowTest { override suspend fun verify(key: PublicKey): Boolean = true } + @Test + fun `connect returns host key rejected when verifier rejects server key`() = runTest { + val dispatcher = StandardTestDispatcher(testScheduler) + val (clientTransport, serverTransport) = PipedTransport.create() + val server = FakeSshServer(serverTransport, backgroundScope, dispatcher) + server.start(ignoreTransportErrors = true) + + val rejectingVerifier = object : HostKeyVerifier { + override suspend fun verify(key: PublicKey): Boolean = false + } + val connection = SshConnection( + transport = clientTransport, + hostKeyVerifier = rejectingVerifier, + coroutineDispatcher = dispatcher, + ) + + try { + assertIs(connectInBackground(connection, backgroundScope, dispatcher)) + } finally { + connection.close() + } + } + + @Test + fun `connect returns algorithm mismatch when kex negotiation has no match`() = runTest { + val dispatcher = StandardTestDispatcher(testScheduler) + val (clientTransport, serverTransport) = PipedTransport.create() + val server = FakeSshServer(serverTransport, backgroundScope, dispatcher) + server.kexAlgorithms = "unsupported-kex@example.com" + server.start(ignoreTransportErrors = true) + + val connection = SshConnection( + transport = clientTransport, + hostKeyVerifier = acceptAllVerifier, + coroutineDispatcher = dispatcher, + ) + + try { + assertIs(connectInBackground(connection, backgroundScope, dispatcher)) + } finally { + connection.close() + } + } + @Test fun `password authentication handles success and failure replies`() = runTest { connectedFixture { connection, server, dispatcher -> @@ -95,6 +139,21 @@ class SshConnectionFlowTest { } } + @Test + fun `public key authentication handles failure reply`() = runTest { + connectedFixture { connection, server, dispatcher -> + val privateKeyData = Files.readString(Paths.get("src/test/resources/keys/ed25519_unencrypted")) + val privateKey = PrivateKeyReader.read(privateKeyData) + + val auth = async(dispatcher) { connection.authenticatePublicKey("user", privateKey) } + val request = withTimeout(5_000) { server.awaitUserauthRequest() } + assertEquals("publickey", request.methodName().value()) + server.sendUserauthFailure(setOf("password"), partialSuccess = false) + + assertIs(withTimeout(5_000) { auth.await() }) + } + } + @Test fun `direct keyboard interactive authentication handles info request`() = runTest { connectedFixture { connection, server, dispatcher -> @@ -123,6 +182,49 @@ class SshConnectionFlowTest { } } + @Test + fun `strategy authentication succeeds when none auth is accepted`() = runTest { + connectedFixture { connection, server, dispatcher -> + val auth = async(dispatcher) { connection.authenticate("user", EmptyAuthHandler()) } + val none = withTimeout(5_000) { server.awaitUserauthRequest() } + assertEquals("none", none.methodName().value()) + server.sendUserauthSuccess() + + assertEquals(AuthResult.Success, withTimeout(5_000) { auth.await() }) + } + } + + @Test + fun `strategy authentication delivers banners while discovering methods`() = runTest { + connectedFixture { connection, server, dispatcher -> + val banners = mutableListOf() + val observedMethods = mutableListOf>() + val handler = object : EmptyAuthHandler() { + override suspend fun onAuthMethodsAvailable(methods: Set) { + observedMethods.add(methods) + } + + override suspend fun onPasswordNeeded(): String = "secret" + + override suspend fun onBanner(message: String) { + banners.add(message) + } + } + + val auth = async(dispatcher) { connection.authenticate("user", handler) } + assertEquals("none", withTimeout(5_000) { server.awaitUserauthRequest() }.methodName().value()) + server.sendUserauthBanner("maintenance window") + server.sendUserauthFailure(setOf("password"), partialSuccess = false) + + assertEquals("password", withTimeout(5_000) { server.awaitUserauthRequest() }.methodName().value()) + server.sendUserauthSuccess() + + assertEquals(AuthResult.Success, withTimeout(5_000) { auth.await() }) + assertEquals(listOf("maintenance window"), banners) + assertEquals(listOf(setOf("password")), observedMethods) + } + } + @Test fun `strategy authentication discovers methods and succeeds with password`() = runTest { connectedFixture { connection, server, dispatcher -> @@ -143,6 +245,17 @@ class SshConnectionFlowTest { } } + @Test + fun `strategy authentication fails when password is unavailable`() = runTest { + connectedFixture { connection, server, dispatcher -> + val auth = async(dispatcher) { connection.authenticate("user", EmptyAuthHandler()) } + assertEquals("none", withTimeout(5_000) { server.awaitUserauthRequest() }.methodName().value()) + server.sendUserauthFailure(setOf("password"), partialSuccess = false) + + assertIs(withTimeout(5_000) { auth.await() }) + } + } + @Test fun `strategy authentication handles keyboard interactive prompt`() = runTest { connectedFixture { connection, server, dispatcher ->