Skip to content

Commit cc309c0

Browse files
committed
chore: add tests for SignatureVerifier
Also trim out testapp/* to keep code coverage up.
1 parent 31b2097 commit cc309c0

2 files changed

Lines changed: 87 additions & 0 deletions

File tree

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ sonar {
4646
property("sonar.organization", "connectbot")
4747
property("sonar.host.url", "https://sonarcloud.io")
4848
property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/kover/report.xml")
49+
property("sonar.coverage.exclusions", "testapp/**")
4950
property("sonar.exclusions", "**/build/generated/**")
5051
property("sonar.issue.ignore.multicriteria", "cognitiveComplexityConnection,cognitiveComplexitySftp,cognitiveComplexityTransport")
5152
property("sonar.issue.ignore.multicriteria.cognitiveComplexityConnection.ruleKey", "kotlin:S3776")

sshlib/src/test/kotlin/org/connectbot/sshlib/crypto/SignatureVerifierTest.kt

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,35 @@ class SignatureVerifierTest {
5959
return buf.toByteArray()
6060
}
6161

62+
private fun buildUnknownHostKey(algorithmName: String = "unknown-key@example.com"): ByteArray {
63+
val buf = ByteArrayOutputStream()
64+
val out = DataOutputStream(buf)
65+
encodeString(out, algorithmName)
66+
encodeString(out, byteArrayOf(1, 2, 3))
67+
return buf.toByteArray()
68+
}
69+
6270
private fun signData(data: ByteArray, jcaAlgorithm: String, kp: java.security.KeyPair): ByteArray {
6371
val sig = Signature.getInstance(jcaAlgorithm)
6472
sig.initSign(kp.private)
6573
sig.update(data)
6674
return sig.sign()
6775
}
6876

77+
private fun readKey(resourcePath: String): SshPrivateKey {
78+
val data = requireNotNull(javaClass.getResourceAsStream("/keys/$resourcePath")) {
79+
"Missing test key resource: /keys/$resourcePath"
80+
}.use { stream ->
81+
stream.bufferedReader().use { reader -> reader.readText() }
82+
}
83+
return PrivateKeyReader.read(data)
84+
}
85+
86+
private fun signWithSshAlgorithm(privateKey: SshPrivateKey, algorithmName: String, data: ByteArray): ByteArray {
87+
val entry = SignatureEntry.fromSshName(algorithmName) ?: error("Unknown algorithm: $algorithmName")
88+
return entry.algorithm.sign(algorithmName, privateKey.jcaKeyPair.private, data)
89+
}
90+
6991
@Test
7092
fun `accepts rsa-sha2-256 signature when rsa-sha2-256 was negotiated`() {
7193
val kp = KeyPairGenerator.getInstance("RSA").apply { initialize(2048) }.generateKeyPair()
@@ -132,4 +154,68 @@ class SignatureVerifierTest {
132154

133155
assertFalse(SignatureVerifier.verify(hostKey, sigBlob, data, "rsa-sha2-256"))
134156
}
157+
158+
@Test
159+
fun `rejects negotiated unknown signature algorithm`() {
160+
val kp = KeyPairGenerator.getInstance("RSA").apply { initialize(2048) }.generateKeyPair()
161+
val data = "exchange hash".toByteArray()
162+
val hostKey = buildRsaHostKey(kp.public as RSAPublicKey)
163+
val sigBlob = buildSignatureBlob("unknown-algo", byteArrayOf(1, 2, 3))
164+
165+
assertFalse(SignatureVerifier.verify(hostKey, sigBlob, data, "unknown-algo"))
166+
}
167+
168+
@Test
169+
fun `verifyWithKeyType accepts RSA-compatible signature algorithms`() {
170+
val privateKey = readKey("rsa_unencrypted")
171+
val data = "session binding".toByteArray()
172+
val hostKey = SshPublicKeyEncoder.encode(privateKey.jcaKeyPair, privateKey.keyType)
173+
174+
for (algorithmName in listOf("ssh-rsa", "rsa-sha2-256", "rsa-sha2-512")) {
175+
val sigBlob = signWithSshAlgorithm(privateKey, algorithmName, data)
176+
177+
assertTrue(SignatureVerifier.verifyWithKeyType(hostKey, sigBlob, data), algorithmName)
178+
}
179+
}
180+
181+
@Test
182+
fun `verifyWithKeyType accepts matching Ed25519 signature algorithm`() {
183+
val privateKey = readKey("ed25519_unencrypted")
184+
val data = "session binding".toByteArray()
185+
val hostKey = SshPublicKeyEncoder.encode(privateKey.jcaKeyPair, privateKey.keyType)
186+
val sigBlob = signWithSshAlgorithm(privateKey, "ssh-ed25519", data)
187+
188+
assertTrue(SignatureVerifier.verifyWithKeyType(hostKey, sigBlob, data))
189+
}
190+
191+
@Test
192+
fun `verifyWithKeyType rejects signature algorithm incompatible with key type`() {
193+
val privateKey = readKey("ed25519_unencrypted")
194+
val rsaKey = readKey("rsa_unencrypted")
195+
val data = "session binding".toByteArray()
196+
val ed25519HostKey = SshPublicKeyEncoder.encode(privateKey.jcaKeyPair, privateKey.keyType)
197+
val rsaSigBlob = signWithSshAlgorithm(rsaKey, "rsa-sha2-256", data)
198+
199+
assertFalse(SignatureVerifier.verifyWithKeyType(ed25519HostKey, rsaSigBlob, data))
200+
}
201+
202+
@Test
203+
fun `verifyWithKeyType rejects non-RSA signature algorithm for RSA key`() {
204+
val rsaKey = readKey("rsa_unencrypted")
205+
val ed25519Key = readKey("ed25519_unencrypted")
206+
val data = "session binding".toByteArray()
207+
val rsaHostKey = SshPublicKeyEncoder.encode(rsaKey.jcaKeyPair, rsaKey.keyType)
208+
val ed25519SigBlob = signWithSshAlgorithm(ed25519Key, "ssh-ed25519", data)
209+
210+
assertFalse(SignatureVerifier.verifyWithKeyType(rsaHostKey, ed25519SigBlob, data))
211+
}
212+
213+
@Test
214+
fun `verifyWithKeyType rejects unknown self-described signature algorithm`() {
215+
val data = "session binding".toByteArray()
216+
val hostKey = buildUnknownHostKey()
217+
val sigBlob = buildSignatureBlob("unknown-key@example.com", byteArrayOf(4, 5, 6))
218+
219+
assertFalse(SignatureVerifier.verifyWithKeyType(hostKey, sigBlob, data))
220+
}
135221
}

0 commit comments

Comments
 (0)