diff --git a/.idea/misc.xml b/.idea/misc.xml index e0c5638..7deb2c1 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,3 +1,4 @@ + diff --git a/build.gradle.kts b/build.gradle.kts index eedf817..37edab0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,14 +13,14 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { - kotlin("multiplatform") version "2.2.21" - id("org.jetbrains.kotlin.plugin.serialization") version "2.2.21" + kotlin("multiplatform") version "2.4.0" + id("org.jetbrains.kotlin.plugin.serialization") version "2.4.0" id("org.jetbrains.dokka") version "1.9.20" `maven-publish` } group = "net.sergeych" -version = "0.9.3" +version = "0.9.4-SNAPSHOT" repositories { mavenCentral() diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt b/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt index e848c7b..84259ad 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt @@ -155,15 +155,9 @@ sealed class Container { abstract val decryptedWithKeyId: KeyId? /** - * If the container _is decrypted by the [EncryptingPublicKey]_, e.g., using secret key encryption, - * contains the [EncryptingPublicKey] that corresponds the [DecryptingSecretKey] used while encrypting, this - * authenticating the sender party cryptographically. This key could be used to encrypt - * the response to be visible to the sender only; the sender, providing it kept his secret key, - * could decrypt it. + * When the container is decrypted, the key that unlocked it, otherwise null. */ - @Transient - var authorisedByKey: EncryptingPublicKey? = null - protected set + abstract val decryptedWithKey: UniversalKey? /** * List of [KeyId] of the keys that unlocked the container, in the same order used for encryption.. @@ -185,7 +179,8 @@ sealed class Container { ) : Container() { @Transient - private var decryptedWithKey: DecryptingKey? = null + override var decryptedWithKey: UniversalKey? = null + private set override val decryptedWithKeyId: KeyId? get() = decryptedWithKey?.id @@ -202,10 +197,7 @@ sealed class Container { if (k.id == keyId) { kotlin.runCatching { k.decrypt(encryptedMessage) }.getOrNull()?.let { decryptedData = it - decryptedWithKey = k - if(k is DecryptingSecretKey) { - authorisedByKey = Asymmetric.Message.decode(encryptedMessage).senderPublicKey - } + decryptedWithKey = k as UniversalKey return it } } @@ -284,9 +276,12 @@ sealed class Container { } @Transient - override var decryptedWithKeyId: KeyId? = null + override var decryptedWithKey: UniversalKey? = null private set + override val decryptedWithKeyId: KeyId? + get() = decryptedWithKey?.id + override val keyIds: List = encryptedKeys.map { it.tag } override fun decryptWith(keyRing: UniversalRing): UByteArray? { @@ -302,11 +297,8 @@ sealed class Container { }.getOrNull()?.let { k -> if (kotlin.runCatching { decryptedData = k.decrypt(encryptedMessage) }.isFailure) throw InvalidContainerException() - decryptedWithKeyId = key.id + decryptedWithKey = key as UniversalKey mainKey = k - if(key is DecryptingSecretKey) { - authorisedByKey = Asymmetric.Message.decode(encryptedKey.cipherData).senderPublicKey - } } if (decryptedData != null) return decryptedData } diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/SafeKeyExchange.kt b/src/commonMain/kotlin/net/sergeych/crypto2/SafeKeyExchange.kt index 4461aba..c84b528 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/SafeKeyExchange.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/SafeKeyExchange.kt @@ -15,6 +15,7 @@ import com.ionspin.kotlin.crypto.keyexchange.KeyExchangeKeyPair import com.ionspin.kotlin.crypto.keyexchange.crypto_kx_PUBLICKEYBYTES import com.ionspin.kotlin.crypto.keyexchange.crypto_kx_SECRETKEYBYTES import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationException import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder @@ -81,7 +82,7 @@ class SafeKeyExchange private constructor( * The session key. It uses a pair of keys to encrypt and decrypt messages to maintain high * security level and allow using counters as nonce with no extra precautions. */ - @Serializable + @Serializable(with = SafeKeyExchange.SessionKeySerializer::class) class SessionKey( val sendingKey: EncryptingKey, val receivingKey: DecryptingKey, @@ -128,6 +129,36 @@ class SafeKeyExchange private constructor( } + @Serializable + private class PackedSessionKey( + val sendingKey: SymmetricKey, + val receivingKey: SymmetricKey, + val isClient: Boolean, + ) + + object SessionKeySerializer : KSerializer { + private val packedSerializer = PackedSessionKey.serializer() + + override val descriptor: SerialDescriptor = packedSerializer.descriptor + + override fun serialize(encoder: Encoder, value: SessionKey) { + val sendingKey = value.sendingKey as? SymmetricKey + ?: throw SerializationException("SessionKey.sendingKey must be SymmetricKey") + val receivingKey = value.receivingKey as? SymmetricKey + ?: throw SerializationException("SessionKey.receivingKey must be SymmetricKey") + + encoder.encodeSerializableValue( + packedSerializer, + PackedSessionKey(sendingKey, receivingKey, value.isClient) + ) + } + + override fun deserialize(decoder: Decoder): SessionKey { + val packed = decoder.decodeSerializableValue(packedSerializer) + return SessionKey(packed.sendingKey, packed.receivingKey, packed.isClient) + } + } + /** * The public key; it should be transmitted to the other party, this is serializable. * Do not use it except to get [SessionKey] with [clientSessionKey] or [serverSessionKey]. Storing and reusing diff --git a/src/commonTest/kotlin/ContainerTest.kt b/src/commonTest/kotlin/ContainerTest.kt index e9ba1c5..ee22ea9 100644 --- a/src/commonTest/kotlin/ContainerTest.kt +++ b/src/commonTest/kotlin/ContainerTest.kt @@ -34,6 +34,8 @@ class ContainerTest { assertNotNull(d) assertContentEquals(data, d) assertTrue { c1.isDecrypted } + assertEquals(syk1.id, c1.decryptedWithKeyId) + assertEquals(syk1, c1.decryptedWithKey) val data2 = "To push unpushinable".encodeToUByteArray() val c2 = Container.decode(c.updateData(data2).encoded) @@ -61,7 +63,7 @@ class ContainerTest { assertContentEquals(data, d) assertTrue { c1.isDecrypted } assertEquals(p2.publicKey.id, c1.decryptedWithKeyId) - assertEquals(p1.publicKey, c1.authorisedByKey) + assertEquals(p2.secretKey, c1.decryptedWithKey) val data2 = "To push unpushinable".encodeToUByteArray() val c2 = Container.decode(c.updateData(data2).encoded) @@ -88,7 +90,7 @@ class ContainerTest { assertContentEquals(data, d) assertTrue { c1.isDecrypted } assertEquals(p4.publicKey.id, c1.decryptedWithKeyId) - assertEquals(p1.publicKey, c1.authorisedByKey) + assertEquals(p4.secretKey, c1.decryptedWithKey) val data2 = "To push unpushinable".encodeToUByteArray() val c2 = Container.decode(c.updateData(data2).encoded) @@ -338,4 +340,4 @@ class ContainerTest { expectOpen(data2, syk3, p3.secretKey, p4.secretKey) } -} \ No newline at end of file +} diff --git a/src/commonTest/kotlin/KeysTest.kt b/src/commonTest/kotlin/KeysTest.kt index 20b274e..0249b02 100644 --- a/src/commonTest/kotlin/KeysTest.kt +++ b/src/commonTest/kotlin/KeysTest.kt @@ -241,6 +241,28 @@ class KeysTest { assertContentEquals(clientSessionKey.sessionTag, serverSessionKey.sessionTag) } + @Test + fun sessionKeySerializationTest() = runTest { + initCrypto() + val ske = SafeKeyExchange() + val cke = SafeKeyExchange() + + val clientSessionKey = cke.clientSessionKey(ske.publicKey) + val serverSessionKey = ske.serverSessionKey(cke.publicKey) + + val restoredClientSessionKey = unpack(pack(clientSessionKey)) + val restoredServerSessionKey = unpack(pack(serverSessionKey)) + + assertEquals(clientSessionKey, restoredClientSessionKey) + assertEquals(serverSessionKey, restoredServerSessionKey) + assertContentEquals(clientSessionKey.sessionTag, restoredClientSessionKey.sessionTag) + assertContentEquals(serverSessionKey.sessionTag, restoredServerSessionKey.sessionTag) + + val src = "Hello from restored session keys!" + assertEquals(src, restoredServerSessionKey.decryptString(restoredClientSessionKey.encrypt(src))) + assertEquals(src, restoredClientSessionKey.decryptString(restoredServerSessionKey.encrypt(src))) + } + @Test fun asymmetricKeyTest() = runTest { initCrypto()