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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ sonar {
property("sonar.organization", "connectbot")
property("sonar.host.url", "https://sonarcloud.io")
property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/kover/report.xml")
property("sonar.coverage.exclusions", "testapp/**")
property("sonar.exclusions", "**/build/generated/**")
property("sonar.issue.ignore.multicriteria", "cognitiveComplexityConnection,cognitiveComplexitySftp,cognitiveComplexityTransport")
property("sonar.issue.ignore.multicriteria.cognitiveComplexityConnection.ruleKey", "kotlin:S3776")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,35 @@ class SignatureVerifierTest {
return buf.toByteArray()
}

private fun buildUnknownHostKey(algorithmName: String = "unknown-key@example.com"): ByteArray {
val buf = ByteArrayOutputStream()
val out = DataOutputStream(buf)
encodeString(out, algorithmName)
encodeString(out, byteArrayOf(1, 2, 3))
return buf.toByteArray()
}

private fun signData(data: ByteArray, jcaAlgorithm: String, kp: java.security.KeyPair): ByteArray {
val sig = Signature.getInstance(jcaAlgorithm)
sig.initSign(kp.private)
sig.update(data)
return sig.sign()
}

private fun readKey(resourcePath: String): SshPrivateKey {
val data = requireNotNull(javaClass.getResourceAsStream("/keys/$resourcePath")) {
"Missing test key resource: /keys/$resourcePath"
}.use { stream ->
stream.bufferedReader().use { reader -> reader.readText() }
}
return PrivateKeyReader.read(data)
Comment thread
kruton marked this conversation as resolved.
}

private fun signWithSshAlgorithm(privateKey: SshPrivateKey, algorithmName: String, data: ByteArray): ByteArray {
val entry = SignatureEntry.fromSshName(algorithmName) ?: error("Unknown algorithm: $algorithmName")
return entry.algorithm.sign(algorithmName, privateKey.jcaKeyPair.private, data)
}

@Test
fun `accepts rsa-sha2-256 signature when rsa-sha2-256 was negotiated`() {
val kp = KeyPairGenerator.getInstance("RSA").apply { initialize(2048) }.generateKeyPair()
Expand Down Expand Up @@ -132,4 +154,68 @@ class SignatureVerifierTest {

assertFalse(SignatureVerifier.verify(hostKey, sigBlob, data, "rsa-sha2-256"))
}

@Test
fun `rejects negotiated unknown signature algorithm`() {
val kp = KeyPairGenerator.getInstance("RSA").apply { initialize(2048) }.generateKeyPair()
val data = "exchange hash".toByteArray()
val hostKey = buildRsaHostKey(kp.public as RSAPublicKey)
val sigBlob = buildSignatureBlob("unknown-algo", byteArrayOf(1, 2, 3))

assertFalse(SignatureVerifier.verify(hostKey, sigBlob, data, "unknown-algo"))
}

@Test
fun `verifyWithKeyType accepts RSA-compatible signature algorithms`() {
val privateKey = readKey("rsa_unencrypted")
val data = "session binding".toByteArray()
val hostKey = SshPublicKeyEncoder.encode(privateKey.jcaKeyPair, privateKey.keyType)

for (algorithmName in listOf("ssh-rsa", "rsa-sha2-256", "rsa-sha2-512")) {
val sigBlob = signWithSshAlgorithm(privateKey, algorithmName, data)

assertTrue(SignatureVerifier.verifyWithKeyType(hostKey, sigBlob, data), algorithmName)
}
}

@Test
fun `verifyWithKeyType accepts matching Ed25519 signature algorithm`() {
val privateKey = readKey("ed25519_unencrypted")
val data = "session binding".toByteArray()
val hostKey = SshPublicKeyEncoder.encode(privateKey.jcaKeyPair, privateKey.keyType)
val sigBlob = signWithSshAlgorithm(privateKey, "ssh-ed25519", data)

assertTrue(SignatureVerifier.verifyWithKeyType(hostKey, sigBlob, data))
}

@Test
fun `verifyWithKeyType rejects signature algorithm incompatible with key type`() {
val privateKey = readKey("ed25519_unencrypted")
val rsaKey = readKey("rsa_unencrypted")
val data = "session binding".toByteArray()
val ed25519HostKey = SshPublicKeyEncoder.encode(privateKey.jcaKeyPair, privateKey.keyType)
val rsaSigBlob = signWithSshAlgorithm(rsaKey, "rsa-sha2-256", data)

assertFalse(SignatureVerifier.verifyWithKeyType(ed25519HostKey, rsaSigBlob, data))
}

@Test
fun `verifyWithKeyType rejects non-RSA signature algorithm for RSA key`() {
val rsaKey = readKey("rsa_unencrypted")
val ed25519Key = readKey("ed25519_unencrypted")
val data = "session binding".toByteArray()
val rsaHostKey = SshPublicKeyEncoder.encode(rsaKey.jcaKeyPair, rsaKey.keyType)
val ed25519SigBlob = signWithSshAlgorithm(ed25519Key, "ssh-ed25519", data)

assertFalse(SignatureVerifier.verifyWithKeyType(rsaHostKey, ed25519SigBlob, data))
}

@Test
fun `verifyWithKeyType rejects unknown self-described signature algorithm`() {
val data = "session binding".toByteArray()
val hostKey = buildUnknownHostKey()
val sigBlob = buildSignatureBlob("unknown-key@example.com", byteArrayOf(4, 5, 6))

assertFalse(SignatureVerifier.verifyWithKeyType(hostKey, sigBlob, data))
}
}
Loading