Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright 2025 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.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class StreamForwarderTest {

@Test
fun `close delegates to stop`() {
var stopCalled = false
val forwarder = object : StreamForwarder {
override val isActive: Boolean get() = !stopCalled
override suspend fun stop() {
stopCalled = true
}
}

assertTrue(forwarder.isActive)
forwarder.close()
assertTrue(stopCalled)
assertFalse(forwarder.isActive)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2025 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.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals

class TinkX25519ProviderTest {
private val provider = TinkX25519Provider()

// RFC 7748 Section 6.1 test vectors
private val alicePrivate = hexToBytes(
"77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a"
)
private val alicePublic = hexToBytes(
"8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a"
)
private val bobPrivate = hexToBytes(
"5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb"
)
private val bobPublic = hexToBytes(
"de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f"
)
private val expectedSharedSecret = hexToBytes(
"4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742"
)

@Test
fun `generatePrivateKey returns 32 bytes`() {
val key = provider.generatePrivateKey()
assertEquals(32, key.size)
}

@Test
fun `generated keys are unique`() {
val key1 = provider.generatePrivateKey()
val key2 = provider.generatePrivateKey()
assert(!key1.contentEquals(key2)) { "Two generated keys should differ" }
}

@Test
fun `publicFromPrivate matches RFC 7748 Alice test vector`() {
val publicKey = provider.publicFromPrivate(alicePrivate)
assertContentEquals(alicePublic, publicKey)
}

@Test
fun `publicFromPrivate matches RFC 7748 Bob test vector`() {
val publicKey = provider.publicFromPrivate(bobPrivate)
assertContentEquals(bobPublic, publicKey)
}

@Test
fun `computeSharedSecret matches RFC 7748 test vector`() {
val secret = provider.computeSharedSecret(alicePrivate, bobPublic)
assertContentEquals(expectedSharedSecret, secret)
}

@Test
fun `shared secret is symmetric`() {
val secretAlice = provider.computeSharedSecret(alicePrivate, bobPublic)
val secretBob = provider.computeSharedSecret(bobPrivate, alicePublic)
assertContentEquals(secretAlice, secretBob)
}

@Test
fun `generatePrivateKey roundtrip through publicFromPrivate and computeSharedSecret`() {
val priv1 = provider.generatePrivateKey()
val pub1 = provider.publicFromPrivate(priv1)
val priv2 = provider.generatePrivateKey()
val pub2 = provider.publicFromPrivate(priv2)

val secret1 = provider.computeSharedSecret(priv1, pub2)
val secret2 = provider.computeSharedSecret(priv2, pub1)
assertContentEquals(secret1, secret2)
}

private fun hexToBytes(hex: String): ByteArray = hex.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Copyright 2025 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.ed25519

import org.junit.Test
import java.security.InvalidKeyException
import java.security.Key
import java.security.KeyFactory
import java.security.PrivateKey
import java.security.PublicKey
import java.security.spec.InvalidKeySpecException
import java.security.spec.KeySpec
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertIs
import kotlin.test.assertSame

class Ed25519KeyFactoryTest {
private val provider = Ed25519Provider()
private val factory = KeyFactory.getInstance(Ed25519Provider.KEY_ALGORITHM, provider)
private val testSeed = ByteArray(32) { it.toByte() }

private fun generateKeyPair(): java.security.KeyPair = Ed25519KeyPairGenerator().generateKeyPair()

@Test
fun `generatePublic with X509EncodedKeySpec`() {
val original = generateKeyPair().public as Ed25519PublicKey
val spec = X509EncodedKeySpec(original.encoded)
val restored = factory.generatePublic(spec)
assertIs<Ed25519PublicKey>(restored)
assertEquals(original, restored)
}

@Test
fun `generatePublic rejects non-X509EncodedKeySpec`() {
val spec = PKCS8EncodedKeySpec(ByteArray(32))
assertFailsWith<InvalidKeySpecException> {
factory.generatePublic(spec)
}
}

@Test
fun `generatePrivate with PKCS8EncodedKeySpec`() {
val original = Ed25519PrivateKey(testSeed)
val spec = PKCS8EncodedKeySpec(original.encoded)
val restored = factory.generatePrivate(spec)
val restoredKey = assertIs<Ed25519PrivateKey>(restored)
assertContentEquals(testSeed, restoredKey.getSeed())
}

@Test
fun `generatePrivate rejects non-PKCS8EncodedKeySpec`() {
val spec = X509EncodedKeySpec(ByteArray(32))
assertFailsWith<InvalidKeySpecException> {
factory.generatePrivate(spec)
}
}

@Test
fun `getKeySpec throws`() {
val key = Ed25519PrivateKey(testSeed)
assertFailsWith<InvalidKeySpecException> {
factory.getKeySpec(key, KeySpec::class.java)
}
}

@Test
fun `translateKey returns same Ed25519PublicKey`() {
val key = generateKeyPair().public as Ed25519PublicKey
val result = factory.translateKey(key)
assertSame(key, result)
}

@Test
fun `translateKey returns same Ed25519PrivateKey`() {
val key = Ed25519PrivateKey(testSeed)
val result = factory.translateKey(key)
assertSame(key, result)
}

@Test
fun `translateKey converts foreign X509 public key`() {
val original = generateKeyPair().public as Ed25519PublicKey
val foreignKey = object : PublicKey {
override fun getAlgorithm() = "EdDSA"
override fun getFormat() = "X.509"
override fun getEncoded() = original.encoded
}
val result = factory.translateKey(foreignKey)
assertIs<Ed25519PublicKey>(result)
assertEquals(original, result)
}

@Test
fun `translateKey converts foreign PKCS#8 private key`() {
val original = Ed25519PrivateKey(testSeed)
val foreignKey = object : PrivateKey {
override fun getAlgorithm() = "EdDSA"
override fun getFormat() = "PKCS#8"
override fun getEncoded() = original.encoded
}
val resultKey = assertIs<Ed25519PrivateKey>(factory.translateKey(foreignKey))
assertContentEquals(testSeed, resultKey.getSeed())
}

@Test
fun `translateKey rejects public key with wrong format`() {
val foreignKey = object : PublicKey {
override fun getAlgorithm() = "EdDSA"
override fun getFormat() = "RAW"
override fun getEncoded() = ByteArray(32)
}
assertFailsWith<InvalidKeyException> {
factory.translateKey(foreignKey)
}
}

@Test
fun `translateKey rejects private key with wrong format`() {
val foreignKey = object : PrivateKey {
override fun getAlgorithm() = "EdDSA"
override fun getFormat() = "RAW"
override fun getEncoded() = ByteArray(32)
}
assertFailsWith<InvalidKeyException> {
factory.translateKey(foreignKey)
}
}

@Test
fun `translateKey rejects unknown key type`() {
val foreignKey = object : Key {
override fun getAlgorithm() = "Unknown"
override fun getFormat() = "Unknown"
override fun getEncoded() = ByteArray(32)
}
assertFailsWith<InvalidKeyException> {
factory.translateKey(foreignKey)
}
}

@Test
fun `translateKey wraps InvalidKeySpecException from bad X509 public key`() {
val foreignKey = object : PublicKey {
override fun getAlgorithm() = "EdDSA"
override fun getFormat() = "X.509"
override fun getEncoded() = ByteArray(64) { 0xFF.toByte() }
}
assertFailsWith<InvalidKeyException> {
factory.translateKey(foreignKey)
}
}

@Test
fun `translateKey wraps InvalidKeySpecException from bad PKCS#8 private key`() {
val foreignKey = object : PrivateKey {
override fun getAlgorithm() = "EdDSA"
override fun getFormat() = "PKCS#8"
override fun getEncoded() = ByteArray(64) { 0xFF.toByte() }
}
assertFailsWith<InvalidKeyException> {
factory.translateKey(foreignKey)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2025 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.ed25519

import org.junit.Test
import java.security.SecureRandom
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertNotNull

class Ed25519KeyPairGeneratorTest {
private val generator = Ed25519KeyPairGenerator()

@Test
fun `generateKeyPair returns non-null key pair`() {
val keyPair = generator.generateKeyPair()
assertNotNull(keyPair)
assertNotNull(keyPair.public)
assertNotNull(keyPair.private)
}

@Test
fun `public key is Ed25519PublicKey`() {
val keyPair = generator.generateKeyPair()
assertIs<Ed25519PublicKey>(keyPair.public)
}

@Test
fun `private key is Ed25519PrivateKey`() {
val keyPair = generator.generateKeyPair()
assertIs<Ed25519PrivateKey>(keyPair.private)
}

@Test
fun `public key has 32 bytes`() {
val keyPair = generator.generateKeyPair()
val pubKey = keyPair.public as Ed25519PublicKey
assertEquals(32, pubKey.getAbyte().size)
}

@Test
fun `private key seed has 32 bytes`() {
val keyPair = generator.generateKeyPair()
val privKey = keyPair.private as Ed25519PrivateKey
assertEquals(32, privKey.getSeed().size)
}

@Test
fun `generated key pairs are unique`() {
val keyPair1 = generator.generateKeyPair()
val keyPair2 = generator.generateKeyPair()
val pub1 = (keyPair1.public as Ed25519PublicKey).getAbyte()
val pub2 = (keyPair2.public as Ed25519PublicKey).getAbyte()
assert(!pub1.contentEquals(pub2)) { "Two generated key pairs should differ" }
}

@Test
fun `initialize does not throw`() {
generator.initialize(256, SecureRandom())
}
}
Loading
Loading