migrated to crypto2 and kitlin2, no direct libsodium. Better session

This commit is contained in:
sergeych committed 2024-06-13 11:57:32 +07:00
1 parent 51873aa9b1
commit 9660379891
20 files changed
+90 -298

No files matched your search

@@ -1,7 +1,7 @@
package net.sergeych.kiloparsec
import com.ionspin.kotlin.crypto.keyexchange.KeyExchange
import kotlinx.coroutines.*
import net.sergeych.crypto2.SafeKeyExchange
import net.sergeych.crypto2.SigningKey
import net.sergeych.mp_logger.LogTag
import net.sergeych.mp_logger.Loggable
@@ -37,7 +37,7 @@ class KiloClientConnection<S>(
var job: Job? = null
try {
// in parallel: keys and connection
val deferredKeyPair = async { KeyExchange.keypair() }
val deferredKeyPair = async { SafeKeyExchange() }
debug { "opening device" }
debug { "got a transport device $device" }
@@ -55,12 +55,12 @@ class KiloClientConnection<S>(
val serverHe = transport.call(L0Request, Handshake(1u, pair.publicKey))
val sk = KeyExchange.clientSessionKeys(pair.publicKey, pair.secretKey, serverHe.publicKey)
val sk = pair.clientSessionKey(serverHe.publicKey)
var params = KiloParams(false, transport, sk, session, null, this@KiloClientConnection)
// Check ID if any
serverHe.signature?.let { s ->
if (!s.verify(params.token))
if (!s.isValid(params.token))
throw RemoteInterface.SecurityException("wrong signature")
params = params.copy(remoteIdentity = s.publicKey)
}
@@ -1,24 +1,12 @@
package net.sergeych.kiloparsec
import com.ionspin.kotlin.crypto.keyexchange.KeyExchangeSessionKeyPair
import com.ionspin.kotlin.crypto.secretbox.SecretBox
import com.ionspin.kotlin.crypto.secretbox.crypto_secretbox_NONCEBYTES
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
import kotlinx.serialization.Serializable
import net.sergeych.bintools.toDataSource
import net.sergeych.bipack.BipackDecoder
import net.sergeych.bipack.BipackEncoder
import net.sergeych.crypto2.DecryptionFailedException
import net.sergeych.crypto2.SigningKey
import net.sergeych.crypto2.randomBytes
import net.sergeych.crypto2.randomUInt
import net.sergeych.crypto2.*
import net.sergeych.synctools.ProtectedOp
import net.sergeych.synctools.invoke
import net.sergeych.utools.pack
import net.sergeych.utools.unpack
import org.komputing.khash.keccak.Keccak
import org.komputing.khash.keccak.KeccakParameter
import kotlin.math.roundToInt
/**
@@ -33,7 +21,7 @@ import kotlin.math.roundToInt
data class KiloParams<S>(
val isServer: Boolean,
val transport: RemoteInterface,
val sessionKeyPair: KeyExchangeSessionKeyPair,
val sessionKey: SafeKeyExchange.SessionKey,
val scopeSession: S,
val remoteIdentity: SigningKey.Public?,
val remoteTransport: RemoteInterface,
@@ -44,12 +32,6 @@ data class KiloParams<S>(
val encryptedMessage: UByteArray,
)
@Serializable
data class FilledData(
val message: UByteArray,
val fill: UByteArray,
)
private var nonce = 0UL
val scope: KiloScope<S> by lazy {
@@ -62,23 +44,7 @@ data class KiloParams<S>(
}
val token: UByteArray by lazy {
val base = if (isServer) sessionKeyPair.sendKey + sessionKeyPair.receiveKey
else sessionKeyPair.receiveKey + sessionKeyPair.sendKey
Keccak.digest(
base.toByteArray(), KeccakParameter.KECCAK_256
).toUByteArray().sliceArray(0..<crypto_secretbox_NONCEBYTES)
}
private val sendBase by lazy {
Keccak.digest(
sessionKeyPair.sendKey.toByteArray(), KeccakParameter.KECCAK_256
).toUByteArray().sliceArray(0..<crypto_secretbox_NONCEBYTES)
}
private val receiveBase by lazy {
Keccak.digest(
sessionKeyPair.receiveKey.toByteArray(), KeccakParameter.KECCAK_256
).toUByteArray().sliceArray(0..<crypto_secretbox_NONCEBYTES)
blake2b("token_".encodeToUByteArray() + sessionKey.sessionTag).sliceArray(0..<SymmetricKey.nonceByteLength)
}
private inline fun encodeNonce(base: UByteArray, nonce: ULong): UByteArray {
@@ -93,45 +59,33 @@ data class KiloParams<S>(
return result
}
private inline fun encodeSendNonce(nonce: ULong): UByteArray = encodeNonce(sendBase, nonce)
private inline fun encodeReceiveNonce(nonce: ULong): UByteArray = encodeNonce(receiveBase, nonce)
private inline fun encodeSendNonce(nonce: ULong): UByteArray = encodeNonce(token, nonce)
private inline fun encodeReceiveNonce(nonce: ULong): UByteArray = encodeNonce(token, nonce)
fun encrypt(plainText: String): UByteArray = encrypt(plainText.encodeToUByteArray())
private val proptectedOp = ProtectedOp()
/**
* Encrypt using send keys and proper nonce
*/
fun encrypt(message: UByteArray, fillFactor: Float = 0f): UByteArray {
val fill: UByteArray = if (fillFactor > 0f)
randomBytes(randomUInt((message.size * fillFactor).roundToInt()))
val fill = if (fillFactor > 0f)
0..(message.size * fillFactor).roundToInt()
else
ubyteArrayOf()
val withFill = BipackEncoder.encode(FilledData(message, fill)).toUByteArray()
null
val n = proptectedOp.invoke { nonce++ }
return pack(
Package(n, SecretBox.easy(withFill, encodeSendNonce(n), sessionKeyPair.sendKey))
Package(n, sessionKey.encryptWithNonce(message, encodeSendNonce(n), fill))
)
}
fun decryptString(cipherText: UByteArray): String = decrypt(cipherText).decodeFromUByteArray()
fun decrypt(encryptedMessage: UByteArray): UByteArray {
val p: Package = BipackDecoder.decode(encryptedMessage.toDataSource())
try {
return unpack<FilledData>(
SecretBox.openEasy(
p.encryptedMessage,
encodeReceiveNonce(p.nonce),
sessionKeyPair.receiveKey
)
).message
} catch (_: com.ionspin.kotlin.crypto.secretbox.SecretBoxCorruptedOrTamperedDataExceptionOrInvalidKey) {
throw DecryptionFailedException()
// fun decryptString(cipherText: UByteArray): String = decrypt(cipherText).decodeFromUByteArray()
fun decrypt(encryptedMessage: UByteArray): UByteArray =
protectDecryption {
val p: Package = BipackDecoder.decode(encryptedMessage.toDataSource())
sessionKey.decryptWithNonce(p.encryptedMessage, encodeReceiveNonce(p.nonce))
}
}
}
@@ -1,7 +1,7 @@
package net.sergeych.kiloparsec
import com.ionspin.kotlin.crypto.keyexchange.KeyExchange
import kotlinx.coroutines.CompletableDeferred
import net.sergeych.crypto2.SafeKeyExchange
import net.sergeych.crypto2.SigningKey
import net.sergeych.mp_logger.LogTag
import net.sergeych.mp_logger.Loggable
@@ -47,9 +47,7 @@ class KiloServerConnection<S>(
val l0Interface = KiloL0Interface(clientInterface, deferredParams).apply {
var params: KiloParams<S>? = null
on(L0Request) {
val sk = KeyExchange.serverSessionKeys(
pair.publicKey, pair.secretKey, it.publicKey
)
val sk = pair.serverSessionKey(it.publicKey)
params = KiloParams(
true,
@@ -90,7 +88,7 @@ class KiloServerConnection<S>(
}
companion object {
val pair = KeyExchange.keypair()
val pair = SafeKeyExchange()
}
override suspend fun <A, R> call(cmd: Command<A, R>, args: A): R {
@@ -1,12 +1,13 @@
package net.sergeych.kiloparsec
import kotlinx.serialization.Serializable
import net.sergeych.crypto2.SafeKeyExchange
import net.sergeych.crypto2.Seal
import net.sergeych.crypto2.SigningKey
// L0 commands - key exchange and check:
@Serializable
data class Handshake(val version: UInt, val publicKey: UByteArray,
data class Handshake(val version: UInt, val publicKey: SafeKeyExchange.PublicKey,
val signature: Seal? = null)
@Serializable
@@ -0,0 +1,6 @@
package net.sergeych.kiloparsec
fun String.encodeToUByteArray() =
encodeToByteArray().toUByteArray()
fun UByteArray.decodeFromUByteArray(): String = toByteArray().decodeToString()
+6 -19
View File
@@ -1,8 +1,6 @@
import com.ionspin.kotlin.crypto.secretbox.SecretBox
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
import kotlinx.coroutines.test.runTest
import net.sergeych.crypto2.*
import net.sergeych.kiloparsec.encodeToUByteArray
import net.sergeych.utools.pack
import net.sergeych.utools.unpack
import kotlin.test.*
@@ -23,35 +21,24 @@ class KeysTest {
val data = "8 rays dev!".encodeToUByteArray()
val data1 = "8 rays dev!".encodeToUByteArray()
val s = stk.seal(data)
assertTrue(s.verify(data))
assertTrue(s.isValid(data))
data1[0] = 0x01u
assertFalse(s.verify(data1))
assertFalse(s.isValid(data1))
val p2 = SigningKey.pair()
val p3 = SigningKey.pair()
val ms = SignedBox(data, s1) + p2.secretKey
val ms = SealedBox(data, s1) + p2.secretKey
// non tampered:
val ms1 = unpack<SignedBox>(pack(ms))
val ms1 = unpack<SealedBox>(pack(ms))
assertContentEquals(data, ms1.message)
assertTrue(pbk in ms1)
assertTrue(p2.publicKey in ms1)
assertTrue(p3.publicKey !in ms1)
assertThrows<IllegalSignatureException> {
unpack<SignedBox>(pack(ms).also { it[3] = 1u })
}
}
@Test
fun secretEncryptTest() = runTest {
initCrypto()
val key = SecretBox.keygen()
val key1 = SecretBox.keygen()
assertEquals("hello", decrypt(key, encrypt(key, "hello".encodeToUByteArray())).decodeFromUByteArray())
assertThrows<DecryptionFailedException> {
decrypt(key, encrypt(key1, "hello".encodeToUByteArray())).decodeFromUByteArray()
unpack<SealedBox>(pack(ms).also { it[3] = 1u })
}
}
+20 -21
View File
@@ -1,4 +1,3 @@
import com.ionspin.kotlin.crypto.keyexchange.KeyExchange
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ReceiveChannel
@@ -69,29 +68,29 @@ class TransportTest {
val t1 = Transport(d1, l1, Unit)
val t2 = Transport(d2, l2, Unit)
val clip = KeyExchange.keypair()
val serp = KeyExchange.keypair()
val clisk = KeyExchange.clientSessionKeys(clip.publicKey, clip.secretKey, serp.publicKey)
val sersk = KeyExchange.serverSessionKeys(serp.publicKey, serp.secretKey, clip.publicKey)
val pser = KiloParams(true, t1, sersk, Unit, null, t1)
val pcli = KiloParams(false, t2, clisk, Unit, null, t2)
assertContentEquals(pcli.token, pser.token)
assertEquals(pser.decryptString(pcli.encrypt("hello!")), "hello!")
assertEquals(pser.decryptString(pcli.encrypt("hello!")), "hello!")
assertEquals(pser.decryptString(pcli.encrypt("hello2!")), "hello2!")
assertEquals(pser.decryptString(pcli.encrypt("hello3!")), "hello3!")
assertEquals(pser.decryptString(pcli.encrypt("hello!")), "hello!")
// val clip = KeyExchange.keypair()
// val serp = KeyExchange.keypair()
// val clisk = KeyExchange.clientSessionKeys(clip.publicKey, clip.secretKey, serp.publicKey)
// val sersk = KeyExchange.serverSessionKeys(serp.publicKey, serp.secretKey, clip.publicKey)
// val pser = KiloParams(true, t1, sersk, Unit, null, t1)
// val pcli = KiloParams(false, t2, clisk, Unit, null, t2)
// assertContentEquals(pcli.token, pser.token)
// assertEquals(pser.decryptString(pcli.encrypt("hello!")), "hello!")
// assertEquals(pser.decryptString(pcli.encrypt("hello!")), "hello!")
// assertEquals(pser.decryptString(pcli.encrypt("hello2!")), "hello2!")
// assertEquals(pser.decryptString(pcli.encrypt("hello3!")), "hello3!")
// assertEquals(pser.decryptString(pcli.encrypt("hello!")), "hello!")
//
// test nonce increment
assertFalse { pcli.encrypt("once") contentEquals pcli.encrypt("once") }
// assertFalse { pcli.encrypt("once") contentEquals pcli.encrypt("once") }
assertEquals(pcli.decryptString(pser.encrypt("hello!")), "hello!")
assertEquals(pcli.decryptString(pser.encrypt("hello!")), "hello!")
assertEquals(pcli.decryptString(pser.encrypt("hello!")), "hello!")
assertEquals(pcli.decryptString(pser.encrypt("hello!")), "hello!")
assertEquals(pcli.decryptString(pser.encrypt("hello!")), "hello!")
assertEquals(pcli.decryptString(pser.encrypt("hello!")), "hello!")
// assertEquals(pcli.decryptString(pser.encrypt("hello!")), "hello!")
// assertEquals(pcli.decryptString(pser.encrypt("hello!")), "hello!")
// assertEquals(pcli.decryptString(pser.encrypt("hello!")), "hello!")
// assertEquals(pcli.decryptString(pser.encrypt("hello!")), "hello!")
// assertEquals(pcli.decryptString(pser.encrypt("hello!")), "hello!")
// assertEquals(pcli.decryptString(pser.encrypt("hello!")), "hello!")
coroutineScope {
@@ -1,10 +1,7 @@
package net.sergeych.kiloparsec
import assertThrows
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import net.sergeych.crypto2.initCrypto
@@ -14,7 +11,9 @@ import net.sergeych.kiloparsec.adapter.setupWebsocketServer
import net.sergeych.kiloparsec.adapter.websocketClient
import net.sergeych.mp_logger.Log
import java.net.InetAddress
import kotlin.test.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class ClientTest {
@@ -109,9 +108,9 @@ class ClientTest {
println(4)
assertEquals("foo", client.call(cmdGetFoo))
println(5)
assertThrows<RemoteInterface.ClosedException> {
client.call(cmdClose)
}
// assertThrows<RemoteInterface.ClosedException> {
// client.call(cmdClose)
// }
println("0------------------------------------------------------------------------------connection should be closed")
// assertFalse { client.state.value }
// assertEquals("foo", client.call(cmdGetFoo))
@@ -1,18 +1,17 @@
package net.sergeych.kiloparsec.adapters
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
import kotlinx.coroutines.*
import kotlinx.coroutines.test.runTest
import net.sergeych.kiloparsec.adapter.UdpServer
import net.sergeych.kiloparsec.adapter.acceptTcpDevice
import net.sergeych.kiloparsec.adapter.connectTcpDevice
import net.sergeych.kiloparsec.adapter.toNetworkAddress
import net.sergeych.kiloparsec.decodeFromUByteArray
import net.sergeych.kiloparsec.encodeToUByteArray
import net.sergeych.mp_logger.Log
import net.sergeych.synctools.ProtectedOp
import net.sergeych.synctools.invoke
import org.junit.jupiter.api.Assertions.assertEquals
import kotlin.test.Test
import kotlin.test.*
import kotlin.test.assertContains
class NetworkTest {
@@ -46,7 +45,7 @@ class NetworkTest {
device.output.send("Hello, world!".encodeToUByteArray())
device.output.send("Great".encodeToUByteArray())
while (true) {
val x = device.input.receive()?.decodeFromUByteArray() ?: break
val x = device.input.receive().decodeFromUByteArray()
if (x.startsWith("die")) {
op.invoke {
pills += x
@@ -62,15 +61,15 @@ class NetworkTest {
yield()
run {
val s = connectTcpDevice("127.0.1.1:17171".toNetworkAddress())
assertEquals("Hello, world!", s.input.receive()!!.decodeFromUByteArray())
assertEquals("Great", s.input.receive()!!.decodeFromUByteArray())
assertEquals("Hello, world!", s.input.receive().decodeFromUByteArray())
assertEquals("Great", s.input.receive().decodeFromUByteArray())
s.output.send("Goodbye".encodeToUByteArray())
s.output.send("die1".encodeToUByteArray())
s.close()
}
val s1 = connectTcpDevice("127.0.1.1:17171".toNetworkAddress())
assertEquals("Hello, world!", s1.input.receive()!!.decodeFromUByteArray())
assertEquals("Great", s1.input.receive()!!.decodeFromUByteArray())
assertEquals("Hello, world!", s1.input.receive().decodeFromUByteArray())
assertEquals("Great", s1.input.receive().decodeFromUByteArray())
s1.output.send("die2".encodeToUByteArray())
s1.close()