Skip to content

Commit bb8554d

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

2 files changed

Lines changed: 85 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: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,33 @@ 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 = javaClass.getResourceAsStream("/keys/$resourcePath")!!
79+
.bufferedReader()
80+
.readText()
81+
return PrivateKeyReader.read(data)
82+
}
83+
84+
private fun signWithSshAlgorithm(privateKey: SshPrivateKey, algorithmName: String, data: ByteArray): ByteArray {
85+
val entry = SignatureEntry.fromSshName(algorithmName) ?: error("Unknown algorithm: $algorithmName")
86+
return entry.algorithm.sign(algorithmName, privateKey.jcaKeyPair.private, data)
87+
}
88+
6989
@Test
7090
fun `accepts rsa-sha2-256 signature when rsa-sha2-256 was negotiated`() {
7191
val kp = KeyPairGenerator.getInstance("RSA").apply { initialize(2048) }.generateKeyPair()
@@ -132,4 +152,68 @@ class SignatureVerifierTest {
132152

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

0 commit comments

Comments
 (0)