refactoring signing keys
This commit is contained in:
parent
e619b45485
commit
7042e41d70
@ -82,7 +82,7 @@ kotlin {
|
|||||||
implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
|
implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
|
||||||
implementation("io.ktor:ktor-server-websockets-jvm:$ktor_version")
|
implementation("io.ktor:ktor-server-websockets-jvm:$ktor_version")
|
||||||
implementation("io.ktor:ktor-server-netty:$ktor_version")
|
implementation("io.ktor:ktor-server-netty:$ktor_version")
|
||||||
implementation("io.ktor:ktor-client-netty:$ktor_version")
|
implementation("io.ktor:ktor-client-cio:$ktor_version")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val jvmTest by getting
|
val jvmTest by getting
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package net.sergeych.crypto
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.LibsodiumInitializer
|
import com.ionspin.kotlin.crypto.LibsodiumInitializer
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
@ -1,4 +1,4 @@
|
|||||||
package net.sergeych.crypto
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
@ -29,14 +29,14 @@ class SignedBox(
|
|||||||
* key, or return unchanged (same) object if it is already signed by this key; you
|
* key, or return unchanged (same) object if it is already signed by this key; you
|
||||||
* _can't assume it always returns a copied object!_
|
* _can't assume it always returns a copied object!_
|
||||||
*/
|
*/
|
||||||
operator fun plus(key: Key.Signing): SignedBox =
|
operator fun plus(key: SigningKey.Secret): SignedBox =
|
||||||
if (key.verifying in this) this
|
if (key.verifying in this) this
|
||||||
else SignedBox(message, seals + Seal.create(key, message), false)
|
else SignedBox(message, seals + Seal.create(key, message), false)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check that it is signed with a specified key.
|
* Check that it is signed with a specified key.
|
||||||
*/
|
*/
|
||||||
operator fun contains(verifyingKey: Key.Verifying): Boolean {
|
operator fun contains(verifyingKey: SigningKey.Public): Boolean {
|
||||||
return seals.any { it.key == verifyingKey }
|
return seals.any { it.key == verifyingKey }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,12 +53,12 @@ class SignedBox(
|
|||||||
* add seals.
|
* add seals.
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Seal(val key: Key.Verifying, val signature: UByteArray) {
|
data class Seal(val key: SigningKey.Public, val signature: UByteArray) {
|
||||||
|
|
||||||
fun verify(message: UByteArray): Boolean = key.verify(signature, message)
|
fun verify(message: UByteArray): Boolean = key.verify(signature, message)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun create(key: Key.Signing, message: UByteArray): Seal {
|
fun create(key: SigningKey.Secret, message: UByteArray): Seal {
|
||||||
return Seal(key.verifying, key.sign(message))
|
return Seal(key.verifying, key.sign(message))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,7 +75,7 @@ class SignedBox(
|
|||||||
* @param keys a list of keys to sign with, should be at least one key.
|
* @param keys a list of keys to sign with, should be at least one key.
|
||||||
* @throws IllegalArgumentException if keys are not specified.
|
* @throws IllegalArgumentException if keys are not specified.
|
||||||
*/
|
*/
|
||||||
operator fun invoke(data: UByteArray, vararg keys: Key.Signing): SignedBox =
|
operator fun invoke(data: UByteArray, vararg keys: SigningKey.Secret): SignedBox =
|
||||||
SignedBox(data, keys.map { Seal.create(it, data) }, false)
|
SignedBox(data, keys.map { Seal.create(it, data) }, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,24 +1,24 @@
|
|||||||
package net.sergeych.crypto
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.signature.InvalidSignatureException
|
import com.ionspin.kotlin.crypto.signature.InvalidSignatureException
|
||||||
import com.ionspin.kotlin.crypto.signature.Signature
|
import com.ionspin.kotlin.crypto.signature.Signature
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import net.sergeych.crypto.Key.Signing
|
import net.sergeych.crypto2.SigningKey.Secret
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keys in general: public, secret and later symmetric too.
|
* Keys in general: public, secret and later symmetric too.
|
||||||
* Keys could be compared to each other for equality and used
|
* Keys could be compared to each other for equality and used
|
||||||
* as a Map keys (not sure about js).
|
* as a Map keys (not sure about js).
|
||||||
*
|
*
|
||||||
* Use [Signing.pair] to create new keys.
|
* Use [Secret.pair] to create new keys.
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
sealed class Key {
|
sealed class SigningKey {
|
||||||
abstract val packed: UByteArray
|
abstract val packed: UByteArray
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
return other is Key && other.packed contentEquals packed
|
return other is SigningKey && other.packed contentEquals packed
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
@ -31,8 +31,8 @@ sealed class Key {
|
|||||||
* Public key to verify signatures only
|
* Public key to verify signatures only
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("pvk")
|
@SerialName("p")
|
||||||
class Verifying(override val packed: UByteArray) : Key() {
|
class Public(override val packed: UByteArray) : SigningKey() {
|
||||||
/**
|
/**
|
||||||
* Verify the signature and return true if it is correct.
|
* Verify the signature and return true if it is correct.
|
||||||
*/
|
*/
|
||||||
@ -51,22 +51,22 @@ sealed class Key {
|
|||||||
* Secret key to sign only
|
* Secret key to sign only
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("ssk")
|
@SerialName("s")
|
||||||
class Signing(override val packed: UByteArray) : Key() {
|
class Secret(override val packed: UByteArray) : SigningKey() {
|
||||||
|
|
||||||
val verifying: Verifying by lazy {
|
val verifying: Public by lazy {
|
||||||
Verifying(Signature.ed25519SkToPk(packed))
|
Public(Signature.ed25519SkToPk(packed))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sign(message: UByteArray): UByteArray = Signature.detached(message, packed)
|
fun sign(message: UByteArray): UByteArray = Signature.detached(message, packed)
|
||||||
override fun toString(): String = "Sct:${super.toString()}"
|
override fun toString(): String = "Sct:${super.toString()}"
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
data class Pair(val signing: Signing, val verifying: Verifying)
|
data class Pair(val signing: Secret, val aPublic: Public)
|
||||||
|
|
||||||
fun pair(): Pair {
|
fun pair(): Pair {
|
||||||
val p = Signature.keypair()
|
val p = Signature.keypair()
|
||||||
return Pair(Signing(p.secretKey), Verifying(p.publicKey))
|
return Pair(Secret(p.secretKey), Public(p.publicKey))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package net.sergeych.crypto
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import net.sergeych.bintools.CRC
|
import net.sergeych.bintools.CRC
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
@file:Suppress("unused")
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package net.sergeych.crypto
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.secretbox.SecretBox
|
import com.ionspin.kotlin.crypto.secretbox.SecretBox
|
||||||
import com.ionspin.kotlin.crypto.secretbox.crypto_secretbox_NONCEBYTES
|
import com.ionspin.kotlin.crypto.secretbox.crypto_secretbox_NONCEBYTES
|
@ -1,4 +1,4 @@
|
|||||||
package net.sergeych.crypto
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
import net.sergeych.bintools.toDump
|
import net.sergeych.bintools.toDump
|
||||||
import net.sergeych.mp_tools.encodeToBase64Url
|
import net.sergeych.mp_tools.encodeToBase64Url
|
@ -6,7 +6,7 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import net.sergeych.crypto.Key
|
import net.sergeych.crypto2.SigningKey
|
||||||
import net.sergeych.mp_logger.LogTag
|
import net.sergeych.mp_logger.LogTag
|
||||||
import net.sergeych.mp_logger.Loggable
|
import net.sergeych.mp_logger.Loggable
|
||||||
import net.sergeych.mp_logger.debug
|
import net.sergeych.mp_logger.debug
|
||||||
@ -21,7 +21,7 @@ import net.sergeych.mp_tools.globalLaunch
|
|||||||
*/
|
*/
|
||||||
class KiloClient<S>(
|
class KiloClient<S>(
|
||||||
localInterface: KiloInterface<S>,
|
localInterface: KiloInterface<S>,
|
||||||
secretKey: Key.Signing? = null,
|
secretKey: SigningKey.Secret? = null,
|
||||||
connectionDataFactory: ConnectionDataFactory<S>,
|
connectionDataFactory: ConnectionDataFactory<S>,
|
||||||
) : RemoteInterface,
|
) : RemoteInterface,
|
||||||
Loggable by LogTag("CLIF") {
|
Loggable by LogTag("CLIF") {
|
||||||
@ -80,7 +80,7 @@ class KiloClient<S>(
|
|||||||
suspend fun token() = deferredClient.await().token()
|
suspend fun token() = deferredClient.await().token()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remote party shared key ([Key.Verifying]]), could be used ti ensure server is what we expected and
|
* Remote party shared key ([SigningKey.Public]]), could be used ti ensure server is what we expected and
|
||||||
* there is no active MITM attack.
|
* there is no active MITM attack.
|
||||||
*
|
*
|
||||||
* Non-null value means the key was successfully authenticated, null means remote party did not provide
|
* Non-null value means the key was successfully authenticated, null means remote party did not provide
|
||||||
@ -99,7 +99,7 @@ class KiloClient<S>(
|
|||||||
}
|
}
|
||||||
private var connectionBuilder: (suspend () -> Transport.Device)? = null
|
private var connectionBuilder: (suspend () -> Transport.Device)? = null
|
||||||
|
|
||||||
var secretIdKey: Key.Signing? = null
|
var secretIdKey: SigningKey.Secret? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build local command implementations (remotely callable ones), exception
|
* Build local command implementations (remotely callable ones), exception
|
||||||
|
@ -2,7 +2,7 @@ package net.sergeych.kiloparsec
|
|||||||
|
|
||||||
import com.ionspin.kotlin.crypto.keyexchange.KeyExchange
|
import com.ionspin.kotlin.crypto.keyexchange.KeyExchange
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import net.sergeych.crypto.Key
|
import net.sergeych.crypto2.SigningKey
|
||||||
import net.sergeych.mp_logger.LogTag
|
import net.sergeych.mp_logger.LogTag
|
||||||
import net.sergeych.mp_logger.Loggable
|
import net.sergeych.mp_logger.Loggable
|
||||||
import net.sergeych.mp_logger.debug
|
import net.sergeych.mp_logger.debug
|
||||||
@ -15,17 +15,17 @@ class KiloClientConnection<S>(
|
|||||||
private val clientInterface: KiloInterface<S>,
|
private val clientInterface: KiloInterface<S>,
|
||||||
private val device: Transport.Device,
|
private val device: Transport.Device,
|
||||||
private val session: S,
|
private val session: S,
|
||||||
private val secretIdKey: Key.Signing? = null,
|
private val secretIdKey: SigningKey.Secret? = null,
|
||||||
) : RemoteInterface, Loggable by LogTag("KPC:${++clientIds}") {
|
) : RemoteInterface, Loggable by LogTag("KPC:${++clientIds}") {
|
||||||
|
|
||||||
constructor(localInterface: KiloInterface<S>, connection: KiloConnectionData<S>, secretIdKey: Key.Signing? = null)
|
constructor(localInterface: KiloInterface<S>, connection: KiloConnectionData<S>, secretIdKey: SigningKey.Secret? = null)
|
||||||
: this(localInterface, connection.device, connection.session, secretIdKey)
|
: this(localInterface, connection.device, connection.session, secretIdKey)
|
||||||
|
|
||||||
private val kiloRemoteInterface = CompletableDeferred<KiloRemoteInterface<S>>()
|
private val kiloRemoteInterface = CompletableDeferred<KiloRemoteInterface<S>>()
|
||||||
|
|
||||||
private val deferredParams = CompletableDeferred<KiloParams<S>>()
|
private val deferredParams = CompletableDeferred<KiloParams<S>>()
|
||||||
|
|
||||||
suspend fun remoteId(): Key.Verifying? = deferredParams.await().remoteIdentity
|
suspend fun remoteId(): SigningKey.Public? = deferredParams.await().remoteIdentity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the client, blocking until the device is closed, or some critical exception
|
* Run the client, blocking until the device is closed, or some critical exception
|
||||||
@ -59,7 +59,7 @@ class KiloClientConnection<S>(
|
|||||||
var params = KiloParams(false, transport, sk, session, null, this@KiloClientConnection)
|
var params = KiloParams(false, transport, sk, session, null, this@KiloClientConnection)
|
||||||
|
|
||||||
// Check ID if any
|
// Check ID if any
|
||||||
serverHe.serverSharedKey?.let { k ->
|
serverHe.serverPublicKey?.let { k ->
|
||||||
if (serverHe.signature == null)
|
if (serverHe.signature == null)
|
||||||
throw RemoteInterface.SecurityException("missing signature")
|
throw RemoteInterface.SecurityException("missing signature")
|
||||||
if (!k.verify(serverHe.signature, params.token))
|
if (!k.verify(serverHe.signature, params.token))
|
||||||
|
@ -9,10 +9,10 @@ import kotlinx.serialization.Serializable
|
|||||||
import net.sergeych.bintools.toDataSource
|
import net.sergeych.bintools.toDataSource
|
||||||
import net.sergeych.bipack.BipackDecoder
|
import net.sergeych.bipack.BipackDecoder
|
||||||
import net.sergeych.bipack.BipackEncoder
|
import net.sergeych.bipack.BipackEncoder
|
||||||
import net.sergeych.crypto.DecryptionFailedException
|
import net.sergeych.crypto2.DecryptionFailedException
|
||||||
import net.sergeych.crypto.Key
|
import net.sergeych.crypto2.SigningKey
|
||||||
import net.sergeych.crypto.randomBytes
|
import net.sergeych.crypto2.randomBytes
|
||||||
import net.sergeych.crypto.randomUInt
|
import net.sergeych.crypto2.randomUInt
|
||||||
import net.sergeych.tools.ProtectedOp
|
import net.sergeych.tools.ProtectedOp
|
||||||
import net.sergeych.utools.pack
|
import net.sergeych.utools.pack
|
||||||
import net.sergeych.utools.unpack
|
import net.sergeych.utools.unpack
|
||||||
@ -34,7 +34,7 @@ data class KiloParams<S>(
|
|||||||
val transport: RemoteInterface,
|
val transport: RemoteInterface,
|
||||||
val sessionKeyPair: KeyExchangeSessionKeyPair,
|
val sessionKeyPair: KeyExchangeSessionKeyPair,
|
||||||
val scopeSession: S,
|
val scopeSession: S,
|
||||||
val remoteIdentity: Key.Verifying?,
|
val remoteIdentity: SigningKey.Public?,
|
||||||
val remoteTransport: RemoteInterface
|
val remoteTransport: RemoteInterface
|
||||||
) {
|
) {
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -56,7 +56,7 @@ data class KiloParams<S>(
|
|||||||
override val session = scopeSession
|
override val session = scopeSession
|
||||||
override val remote: RemoteInterface = remoteTransport
|
override val remote: RemoteInterface = remoteTransport
|
||||||
override val sessionToken: UByteArray = token
|
override val sessionToken: UByteArray = token
|
||||||
override val remoteIdentity: Key.Verifying? = this@KiloParams.remoteIdentity
|
override val remoteIdentity: SigningKey.Public? = this@KiloParams.remoteIdentity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package net.sergeych.kiloparsec
|
package net.sergeych.kiloparsec
|
||||||
|
|
||||||
import net.sergeych.crypto.Key
|
import net.sergeych.crypto2.SigningKey
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scope for Kiloparsec client/server commands execution, contain per-connection specific data. The scope
|
* Scope for Kiloparsec client/server commands execution, contain per-connection specific data. The scope
|
||||||
@ -26,8 +26,8 @@ interface KiloScope<S> {
|
|||||||
val sessionToken: UByteArray
|
val sessionToken: UByteArray
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the remote part has provided a secret key, e.g., gave non-null [Key.Signing] on construction,
|
* If the remote part has provided a secret key, e.g., gave non-null [SigningKey.Secret] on construction,
|
||||||
* the kiloparsec checks it in the MITM-safe way and provides its [Key.Verifying] shared key here.
|
* the kiloparsec checks it in the MITM-safe way and provides its [SigningKey.Public] shared key here.
|
||||||
* Knowing a remote party shared key, it is possible to be sure that the connection is made directly
|
* Knowing a remote party shared key, it is possible to be sure that the connection is made directly
|
||||||
* to this party with no middle point intruders.
|
* to this party with no middle point intruders.
|
||||||
*
|
*
|
||||||
@ -37,6 +37,6 @@ interface KiloScope<S> {
|
|||||||
* In spite of the above said, which means, non-null value in this field means the key is authorized, but
|
* In spite of the above said, which means, non-null value in this field means the key is authorized, but
|
||||||
* It is up to the caller to ensure it is expected key of the remote party.
|
* It is up to the caller to ensure it is expected key of the remote party.
|
||||||
*/
|
*/
|
||||||
val remoteIdentity: Key.Verifying?
|
val remoteIdentity: SigningKey.Public?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ package net.sergeych.kiloparsec
|
|||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import net.sergeych.crypto.Key
|
import net.sergeych.crypto2.SigningKey
|
||||||
import net.sergeych.kiloparsec.adapter.InetTransportDevice
|
import net.sergeych.kiloparsec.adapter.InetTransportDevice
|
||||||
import net.sergeych.mp_logger.LogTag
|
import net.sergeych.mp_logger.LogTag
|
||||||
import net.sergeych.mp_logger.debug
|
import net.sergeych.mp_logger.debug
|
||||||
@ -17,7 +17,7 @@ private val instances = AtomicCounter()
|
|||||||
class KiloServer<S>(
|
class KiloServer<S>(
|
||||||
private val clientInterface: KiloInterface<S>,
|
private val clientInterface: KiloInterface<S>,
|
||||||
private val connections: Flow<InetTransportDevice>,
|
private val connections: Flow<InetTransportDevice>,
|
||||||
private val serverSigningKey: Key.Signing? = null,
|
private val serverSecretKey: SigningKey.Secret? = null,
|
||||||
private val sessionBuilder: ()->S,
|
private val sessionBuilder: ()->S,
|
||||||
): LogTag("KS:${instances.incrementAndGet()}") {
|
): LogTag("KS:${instances.incrementAndGet()}") {
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ class KiloServer<S>(
|
|||||||
launch {
|
launch {
|
||||||
try {
|
try {
|
||||||
info { "connected ${device}" }
|
info { "connected ${device}" }
|
||||||
KiloServerConnection(clientInterface, device, sessionBuilder(), serverSigningKey)
|
KiloServerConnection(clientInterface, device, sessionBuilder(), serverSecretKey)
|
||||||
.apply { debug { "server connection is ready" }}
|
.apply { debug { "server connection is ready" }}
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package net.sergeych.kiloparsec
|
|||||||
|
|
||||||
import com.ionspin.kotlin.crypto.keyexchange.KeyExchange
|
import com.ionspin.kotlin.crypto.keyexchange.KeyExchange
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import net.sergeych.crypto.Key
|
import net.sergeych.crypto2.SigningKey
|
||||||
import net.sergeych.mp_logger.LogTag
|
import net.sergeych.mp_logger.LogTag
|
||||||
import net.sergeych.mp_logger.Loggable
|
import net.sergeych.mp_logger.Loggable
|
||||||
import net.sergeych.mp_logger.debug
|
import net.sergeych.mp_logger.debug
|
||||||
@ -23,15 +23,15 @@ class KiloServerConnection<S>(
|
|||||||
private val clientInterface: KiloInterface<S>,
|
private val clientInterface: KiloInterface<S>,
|
||||||
private val device: Transport.Device,
|
private val device: Transport.Device,
|
||||||
private val session: S,
|
private val session: S,
|
||||||
private val serverSigningKey: Key.Signing? = null
|
private val serverSigningKey: SigningKey.Secret? = null
|
||||||
) : RemoteInterface, Loggable by LogTag("SRV${++serverIds}") {
|
) : RemoteInterface, Loggable by LogTag("SRV${++serverIds}") {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcut to construct with [KiloConnectionData] intance
|
* Shortcut to construct with [KiloConnectionData] intance
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
constructor(localInterface: KiloInterface<S>, connection: KiloConnectionData<S>, serverSigningKey: Key.Signing? = null)
|
constructor(localInterface: KiloInterface<S>, connection: KiloConnectionData<S>, serverSecretKey: SigningKey.Secret? = null)
|
||||||
: this(localInterface, connection.device, connection.session, serverSigningKey)
|
: this(localInterface, connection.device, connection.session, serverSecretKey)
|
||||||
|
|
||||||
private val kiloRemoteInterface = CompletableDeferred<KiloRemoteInterface<S>>()
|
private val kiloRemoteInterface = CompletableDeferred<KiloRemoteInterface<S>>()
|
||||||
|
|
||||||
@ -60,13 +60,13 @@ class KiloServerConnection<S>(
|
|||||||
this@KiloServerConnection
|
this@KiloServerConnection
|
||||||
)
|
)
|
||||||
|
|
||||||
var verifying: Key.Verifying? = null
|
var public: SigningKey.Public? = null
|
||||||
var signature: UByteArray? = null
|
var signature: UByteArray? = null
|
||||||
if( serverSigningKey != null ) {
|
if( serverSigningKey != null ) {
|
||||||
verifying = serverSigningKey.verifying
|
public = serverSigningKey.verifying
|
||||||
signature = serverSigningKey.sign(params!!.token)
|
signature = serverSigningKey.sign(params!!.token)
|
||||||
}
|
}
|
||||||
Handshake(1u, pair.publicKey, verifying, signature)
|
Handshake(1u, pair.publicKey, public, signature)
|
||||||
}
|
}
|
||||||
on(L0ClientId) {
|
on(L0ClientId) {
|
||||||
var p = params ?: throw RemoteInterface.ClosedException("wrong handshake sequence")
|
var p = params ?: throw RemoteInterface.ClosedException("wrong handshake sequence")
|
||||||
|
@ -13,7 +13,7 @@ import kotlinx.serialization.descriptors.SerialDescriptor
|
|||||||
import kotlinx.serialization.encoding.Decoder
|
import kotlinx.serialization.encoding.Decoder
|
||||||
import kotlinx.serialization.encoding.Encoder
|
import kotlinx.serialization.encoding.Encoder
|
||||||
import kotlinx.serialization.serializer
|
import kotlinx.serialization.serializer
|
||||||
import net.sergeych.crypto.toDump
|
import net.sergeych.crypto2.toDump
|
||||||
import net.sergeych.kiloparsec.Transport.Device
|
import net.sergeych.kiloparsec.Transport.Device
|
||||||
import net.sergeych.mp_logger.*
|
import net.sergeych.mp_logger.*
|
||||||
import net.sergeych.utools.pack
|
import net.sergeych.utools.pack
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
package net.sergeych.kiloparsec.adapter
|
||||||
|
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.plugins.websocket.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import io.ktor.websocket.*
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import net.sergeych.crypto2.SigningKey
|
||||||
|
import net.sergeych.kiloparsec.KiloClient
|
||||||
|
import net.sergeych.kiloparsec.KiloConnectionData
|
||||||
|
import net.sergeych.kiloparsec.KiloInterface
|
||||||
|
import net.sergeych.mp_logger.LogTag
|
||||||
|
import net.sergeych.mp_logger.exception
|
||||||
|
import net.sergeych.mp_logger.info
|
||||||
|
import net.sergeych.mp_logger.warning
|
||||||
|
import net.sergeych.mp_tools.globalLaunch
|
||||||
|
import net.sergeych.tools.AtomicCounter
|
||||||
|
|
||||||
|
private val counter = AtomicCounter()
|
||||||
|
|
||||||
|
fun <S>websocketClient(
|
||||||
|
path: String,
|
||||||
|
clientInterface: KiloInterface<S> = KiloInterface(),
|
||||||
|
client: HttpClient = HttpClient { install(WebSockets) },
|
||||||
|
secretKey: SigningKey.Secret? = null,
|
||||||
|
sessionMaker: () -> S,
|
||||||
|
): KiloClient<S> {
|
||||||
|
var u = Url(path)
|
||||||
|
if (u.encodedPath.length <= 1)
|
||||||
|
u = URLBuilder(u).apply {
|
||||||
|
encodedPath = "/kp"
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
return KiloClient(clientInterface, secretKey) {
|
||||||
|
val input = Channel<UByteArray>()
|
||||||
|
val output = Channel<UByteArray>()
|
||||||
|
globalLaunch {
|
||||||
|
val log = LogTag("KC:${counter.incrementAndGet()}:$u")
|
||||||
|
client.webSocket({
|
||||||
|
url.protocol = u.protocol
|
||||||
|
url.host = u.host
|
||||||
|
url.encodedPath = u.encodedPath
|
||||||
|
url.parameters.appendAll(u.parameters)
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
log.info { "connected to server" }
|
||||||
|
launch {
|
||||||
|
for (block in output) {
|
||||||
|
send(block.toByteArray())
|
||||||
|
}
|
||||||
|
log.info { "input is closed, closing the websocket" }
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
for (f in incoming) {
|
||||||
|
if (f is Frame.Binary) {
|
||||||
|
input.send(f.readBytes().toUByteArray())
|
||||||
|
} else {
|
||||||
|
log.warning { "ignoring unexpected frame of type ${f.frameType}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(_:CancellationException) {
|
||||||
|
}
|
||||||
|
catch(t: Throwable) {
|
||||||
|
log.exception { "unexpected error" to t }
|
||||||
|
}
|
||||||
|
log.info { "closing connection" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val device = ProxyDevice(input,output) { input.close() }
|
||||||
|
KiloConnectionData(device, sessionMaker())
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,16 @@
|
|||||||
package net.sergeych.kiloparsec
|
package net.sergeych.kiloparsec
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import net.sergeych.crypto.Key
|
import net.sergeych.crypto2.SigningKey
|
||||||
|
|
||||||
// L0 commands - key exchange and check:
|
// L0 commands - key exchange and check:
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Handshake(val version: UInt, val publicKey: UByteArray,
|
data class Handshake(val version: UInt, val publicKey: UByteArray,
|
||||||
val serverSharedKey: Key.Verifying? = null,
|
val serverPublicKey: SigningKey.Public? = null,
|
||||||
val signature: UByteArray? = null)
|
val signature: UByteArray? = null)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ClientIdentity(val clientIdKey: Key.Verifying?, val signature: UByteArray?)
|
data class ClientIdentity(val clientIdKey: SigningKey.Public?, val signature: UByteArray?)
|
||||||
|
|
||||||
// Level 0 command: request key exchange
|
// Level 0 command: request key exchange
|
||||||
internal val L0Request by command<Handshake, Handshake>()
|
internal val L0Request by command<Handshake, Handshake>()
|
||||||
|
@ -2,7 +2,7 @@ import com.ionspin.kotlin.crypto.secretbox.SecretBox
|
|||||||
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
|
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
|
||||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.crypto.*
|
import net.sergeych.crypto2.*
|
||||||
import net.sergeych.utools.pack
|
import net.sergeych.utools.pack
|
||||||
import net.sergeych.utools.unpack
|
import net.sergeych.utools.unpack
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
@ -11,11 +11,11 @@ class KeysTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testCreationAndMap() = runTest {
|
fun testCreationAndMap() = runTest {
|
||||||
initCrypto()
|
initCrypto()
|
||||||
val (stk,pbk) = Key.Signing.pair()
|
val (stk,pbk) = SigningKey.Secret.pair()
|
||||||
|
|
||||||
val x = mapOf( stk to "STK!", pbk to "PBK!")
|
val x = mapOf( stk to "STK!", pbk to "PBK!")
|
||||||
assertEquals("STK!", x[stk])
|
assertEquals("STK!", x[stk])
|
||||||
val s1 = Key.Signing(stk.packed)
|
val s1 = SigningKey.Secret(stk.packed)
|
||||||
assertEquals(stk, s1)
|
assertEquals(stk, s1)
|
||||||
assertEquals("STK!", x[s1])
|
assertEquals("STK!", x[s1])
|
||||||
assertEquals("PBK!", x[pbk])
|
assertEquals("PBK!", x[pbk])
|
||||||
@ -27,8 +27,8 @@ class KeysTest {
|
|||||||
|
|
||||||
data1[0] = 0x01u
|
data1[0] = 0x01u
|
||||||
assertFalse(s.verify(data1))
|
assertFalse(s.verify(data1))
|
||||||
val p2 = Key.Signing.pair()
|
val p2 = SigningKey.Secret.pair()
|
||||||
val p3 = Key.Signing.pair()
|
val p3 = SigningKey.Secret.pair()
|
||||||
|
|
||||||
val ms = SignedBox(data, s1) + p2.signing
|
val ms = SignedBox(data, s1) + p2.signing
|
||||||
|
|
||||||
@ -36,8 +36,8 @@ class KeysTest {
|
|||||||
val ms1 = unpack<SignedBox>(pack(ms))
|
val ms1 = unpack<SignedBox>(pack(ms))
|
||||||
assertContentEquals(data, ms1.message)
|
assertContentEquals(data, ms1.message)
|
||||||
assertTrue(pbk in ms1)
|
assertTrue(pbk in ms1)
|
||||||
assertTrue(p2.verifying in ms1)
|
assertTrue(p2.aPublic in ms1)
|
||||||
assertTrue(p3.verifying !in ms1)
|
assertTrue(p3.aPublic !in ms1)
|
||||||
|
|
||||||
assertThrows<IllegalSignatureException> {
|
assertThrows<IllegalSignatureException> {
|
||||||
unpack<SignedBox>(pack(ms).also { it[3] = 1u })
|
unpack<SignedBox>(pack(ms).also { it[3] = 1u })
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
import net.sergeych.crypto.initCrypto
|
import net.sergeych.crypto2.initCrypto
|
||||||
import net.sergeych.kiloparsec.Transport
|
import net.sergeych.kiloparsec.Transport
|
||||||
import net.sergeych.utools.nowToSeconds
|
import net.sergeych.utools.nowToSeconds
|
||||||
import net.sergeych.utools.pack
|
import net.sergeych.utools.pack
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.crypto.createContrail
|
import net.sergeych.crypto2.createContrail
|
||||||
import net.sergeych.crypto.initCrypto
|
import net.sergeych.crypto2.initCrypto
|
||||||
import net.sergeych.crypto.isValidContrail
|
import net.sergeych.crypto2.isValidContrail
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFalse
|
import kotlin.test.assertFalse
|
||||||
|
@ -4,8 +4,8 @@ import kotlinx.coroutines.channels.Channel
|
|||||||
import kotlinx.coroutines.channels.ReceiveChannel
|
import kotlinx.coroutines.channels.ReceiveChannel
|
||||||
import kotlinx.coroutines.channels.SendChannel
|
import kotlinx.coroutines.channels.SendChannel
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.crypto.Key
|
import net.sergeych.crypto2.SigningKey
|
||||||
import net.sergeych.crypto.initCrypto
|
import net.sergeych.crypto2.initCrypto
|
||||||
import net.sergeych.kiloparsec.*
|
import net.sergeych.kiloparsec.*
|
||||||
import net.sergeych.mp_logger.Log
|
import net.sergeych.mp_logger.Log
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
@ -162,7 +162,7 @@ class TransportTest {
|
|||||||
val cmdPing by command<String, String>()
|
val cmdPing by command<String, String>()
|
||||||
val cmdPush by command<String, String>()
|
val cmdPush by command<String, String>()
|
||||||
val cmdGetToken by command<Unit, UByteArray>()
|
val cmdGetToken by command<Unit, UByteArray>()
|
||||||
val cmdGetClientId by command<Unit, Key.Verifying?>()
|
val cmdGetClientId by command<Unit, SigningKey.Public?>()
|
||||||
val cmdChainCallServer1 by command<String, String>()
|
val cmdChainCallServer1 by command<String, String>()
|
||||||
val cmdChainCallClient1 by command<String, String>()
|
val cmdChainCallClient1 by command<String, String>()
|
||||||
val cmdChainCallServer2 by command<String, String>()
|
val cmdChainCallServer2 by command<String, String>()
|
||||||
@ -171,8 +171,8 @@ class TransportTest {
|
|||||||
// Log.defaultLevel = Log.Level.DEBUG
|
// Log.defaultLevel = Log.Level.DEBUG
|
||||||
val (d1, d2) = createTestDevice()
|
val (d1, d2) = createTestDevice()
|
||||||
|
|
||||||
val serverId = Key.Signing.pair()
|
val serverId = SigningKey.Secret.pair()
|
||||||
val clientId = Key.Signing.pair()
|
val clientId = SigningKey.Secret.pair()
|
||||||
|
|
||||||
val serverInterface = KiloInterface<String>().apply {
|
val serverInterface = KiloInterface<String>().apply {
|
||||||
on(cmdPing) {
|
on(cmdPing) {
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
package net.sergeych.kiloparsec.adapter
|
||||||
|
|
||||||
|
import io.ktor.server.application.*
|
||||||
|
import io.ktor.server.routing.*
|
||||||
|
import io.ktor.server.websocket.*
|
||||||
|
import io.ktor.websocket.*
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import net.sergeych.crypto2.SigningKey
|
||||||
|
import net.sergeych.kiloparsec.KiloInterface
|
||||||
|
import net.sergeych.kiloparsec.KiloServerConnection
|
||||||
|
import net.sergeych.mp_logger.LogTag
|
||||||
|
import net.sergeych.mp_logger.debug
|
||||||
|
import net.sergeych.mp_logger.warning
|
||||||
|
import net.sergeych.tools.AtomicCounter
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
|
fun <S> Application.setupWebsocketServer(
|
||||||
|
localInterface: KiloInterface<S>,
|
||||||
|
path: String = "/kp",
|
||||||
|
serverKey: SigningKey.Secret? = null,
|
||||||
|
createSession: () -> S,
|
||||||
|
) {
|
||||||
|
|
||||||
|
install(Routing)
|
||||||
|
install(WebSockets) {
|
||||||
|
pingPeriod = Duration.ofSeconds(15)
|
||||||
|
timeout = Duration.ofSeconds(15)
|
||||||
|
maxFrameSize = Long.MAX_VALUE
|
||||||
|
masking = false
|
||||||
|
}
|
||||||
|
val counter = AtomicCounter()
|
||||||
|
routing {
|
||||||
|
webSocket(path) {
|
||||||
|
val log = LogTag("KWS:${counter.incrementAndGet()}")
|
||||||
|
log.debug { "opening the connection" }
|
||||||
|
val input = Channel<UByteArray>(256)
|
||||||
|
val output = Channel<UByteArray>(256)
|
||||||
|
launch {
|
||||||
|
while (isActive) {
|
||||||
|
send(output.receive().toByteArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val server = KiloServerConnection(
|
||||||
|
localInterface,
|
||||||
|
ProxyDevice(input, output) { input.close() },
|
||||||
|
createSession(),
|
||||||
|
serverKey
|
||||||
|
)
|
||||||
|
launch { server.run() }
|
||||||
|
for( f in incoming) {
|
||||||
|
if (f is Frame.Binary)
|
||||||
|
input.send(f.readBytes().toUByteArray())
|
||||||
|
else
|
||||||
|
log.warning { "unknown frame type ${f.frameType}, ignoring" }
|
||||||
|
|
||||||
|
}
|
||||||
|
log.debug { "closing the server" }
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,8 +4,8 @@ import kotlinx.coroutines.*
|
|||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
import kotlinx.coroutines.channels.ClosedReceiveChannelException
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import net.sergeych.crypto.encodeVarUnsigned
|
import net.sergeych.crypto2.encodeVarUnsigned
|
||||||
import net.sergeych.crypto.readVarUnsigned
|
import net.sergeych.crypto2.readVarUnsigned
|
||||||
import net.sergeych.kiloparsec.Transport
|
import net.sergeych.kiloparsec.Transport
|
||||||
import net.sergeych.mp_logger.LogTag
|
import net.sergeych.mp_logger.LogTag
|
||||||
import net.sergeych.mp_logger.warning
|
import net.sergeych.mp_logger.warning
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.sergeych.kiloparsec
|
package net.sergeych.kiloparsec
|
||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.crypto.initCrypto
|
import net.sergeych.crypto2.initCrypto
|
||||||
import net.sergeych.kiloparsec.adapter.acceptTcpDevice
|
import net.sergeych.kiloparsec.adapter.acceptTcpDevice
|
||||||
import net.sergeych.kiloparsec.adapter.connectTcpDevice
|
import net.sergeych.kiloparsec.adapter.connectTcpDevice
|
||||||
import net.sergeych.mp_logger.Log
|
import net.sergeych.mp_logger.Log
|
||||||
@ -49,4 +49,9 @@ class ClientTest {
|
|||||||
// client.close()
|
// client.close()
|
||||||
// Todo
|
// Todo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun webSocketTest() = runTest {
|
||||||
|
// val server = setupWebsoketServer()
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user