diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/Asymmetric.kt b/src/commonMain/kotlin/net/sergeych/crypto2/Asymmetric.kt index 6932a16..c449cbb 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/Asymmetric.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/Asymmetric.kt @@ -256,4 +256,11 @@ object Asymmetric { get() = 0 } -} \ No newline at end of file + +} + +/** + * Shortcut type: a pair of sender secret key and recipient private key could be used so + * simplify such interfaces + */ +typealias AsymmetricEncryptionPair = Pair diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt b/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt index b19328f..8472951 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt @@ -61,11 +61,20 @@ sealed class Container { */ @Serializable @SerialName("1") - internal class Single(val keyTag: KeyTag, val encryptedMessage: UByteArray) : Container() { + internal class Single( + val keyTag: KeyTag, val encryptedMessage: UByteArray, + @Transient private val creationData: UByteArray? = null, + @Transient private val reEncryptionKey: EncryptingKey? = null, + @Transient private var encryptionPair: AsymmetricEncryptionPair? = null, + ) : Container() { @Transient private var decryptedWithKey: DecryptingKey? = null + init { + decryptedData = creationData + } + override fun decryptWith(keyRing: UniversalRing): UByteArray? { decryptedData?.let { return it } for (k in keyRing) { @@ -83,25 +92,34 @@ sealed class Container { internal val asOpenMulti: Container by lazy { check(isDecrypted) { "container should be decrypted" } create(decryptedData!!) { - alwaysMulti() - when (val k = decryptedWithKey!!) { - is Asymmetric.SecretKey -> { - key(k.publicKey) - } + asOpenedMulti() + // The encryption key is known if we just created the container + if (reEncryptionKey != null) + key(reEncryptionKey) + else if (encryptionPair != null) { + key(encryptionPair!!) + } else { + // otherwise, we don't know the encryption key and will try to derive it + // from the decryption key: + when (val k = decryptedWithKey!!) { + is Asymmetric.SecretKey -> { + key(k.publicKey) + } - is EncryptingKey -> { - key(k) - } + is EncryptingKey -> { + key(k) + } - is UniversalKey.Secret -> { - key(k.key.publicKey) - } + is UniversalKey.Secret -> { + key(k.key.publicKey) + } - else -> { - throw IllegalStateException("unknown key type to convert container: ${k::class.simpleName}") + else -> { + throw IllegalStateException("unknown key type to convert container: ${k::class.simpleName}") + } } } - }.apply { decryptWith(decryptedWithKey!!) ?: throw Crypto2Exception("internal error in container update (1)") } + } } } @@ -111,7 +129,12 @@ sealed class Container { */ @Serializable @SerialName("*") - internal class Multi(val encryptedKeys: List, val encryptedMessage: UByteArray) : Container() { + internal class Multi( + val encryptedKeys: List, val encryptedMessage: UByteArray, + @Transient internal var mainKey: SymmetricKey? = null, + @Transient internal var knownPlainData: UByteArray? = null, + + ) : Container() { @Serializable class EncryptedKey(val tag: KeyTag, val cipherData: UByteArray) { constructor(key: EncryptingKey, encodeMainKey: UByteArray) : @@ -127,7 +150,9 @@ sealed class Container { ) } - internal var mainKey: SymmetricKey? = null + init { + knownPlainData?.let { decryptedData = it } + } override fun decryptWith(keyRing: UniversalRing): UByteArray? { decryptedData?.let { return it } @@ -164,17 +189,22 @@ sealed class Container { } /** - * Add e key to the __decrypted__ container + * Add e key to the __decrypted__ container. The new container is also decrypted so you can add + * more keys, etc. */ operator fun plus(recipient: Asymmetric.PublicKey) = addRecipients { key(recipient) } + /** - * Add e key to the __decrypted__ container + * Add e key to the __decrypted__ container. The new container is also decrypted so you can add + * more keys, etc. */ operator fun plus(recipient: EncryptingKey) = addRecipients { key(recipient) } + /** - * Add e sender -> recipient asymmetric keys pair key to the __decrypted__ container + * Add e key to the __decrypted__ container. The new container is also decrypted so you can add + * more keys, etc. */ - operator fun plus(pair: Pair) = addRecipients { key(pair) } + operator fun plus(pair: Pair) = addRecipients { key(pair) } /** * Binary encoded version. It is desirable to include [Container] as an object, though, @@ -222,7 +252,7 @@ sealed class Container { private val plainKeys = mutableListOf() - private val keyPairs = mutableListOf>() + private val keyPairs = mutableListOf() private var fillRange: IntRange? = null /** @@ -236,7 +266,7 @@ sealed class Container { * Add one or more [Asymmetric.SecretKey] as sender authority coupled with [Asymmetric.PublicKey] as * a recipient. This is faster than anonymous usage of [Asymmetric.PublicKey] only */ - fun key(vararg pairs: Pair) { + fun key(vararg pairs: AsymmetricEncryptionPair) { keyPairs.addAll(pairs) } @@ -256,14 +286,14 @@ sealed class Container { fillRange = range } - private var makeMulti = false + private var makeOpenedMulti = false /** * @suppress * will produce multikey internal variant even with only one key. User internally */ - internal fun alwaysMulti() { - makeMulti = true + internal fun asOpenedMulti() { + makeOpenedMulti = true } /** @@ -274,7 +304,7 @@ sealed class Container { if (parent != null) require(parent is Multi) { "parent container mut be a multikey variant" } return when { countNewKeys == 0 -> throw IllegalArgumentException("Container needs at least one key") - countNewKeys == 1 && makeMulti == false && parent == null -> { + countNewKeys == 1 && makeOpenedMulti == false && parent == null -> { createSingle() } @@ -297,16 +327,20 @@ sealed class Container { } private fun createSingle() = plainKeys.firstOrNull()?.let { - Single(it.tag, it.encrypt(plainData, fillRange)) + Single(it.tag, it.encrypt(plainData, fillRange), plainData, reEncryptionKey = it) } ?: run { - val (sk, pk) = keyPairs.first() + val pair = keyPairs.first() + val (sk, pk) = pair Single( pk.tag, pk.encryptMessage( plainData, senderKey = sk ?: Asymmetric.randomSecretKey(), randomFill = fillRange - ).encoded + ).encoded, + plainData, + encryptionPair = pair ) + } private fun createMulti( @@ -320,7 +354,10 @@ sealed class Container { val (sender, recipient) = p eks += Multi.EncryptedKey(sender, recipient, encodedMainKey) } - return Multi(eks, mainKey.encrypt(plainData, fillRange)) + return if (makeOpenedMulti) + Multi(eks, mainKey.encrypt(plainData, fillRange), mainKey, plainData) + else + Multi(eks, mainKey.encrypt(plainData, fillRange)) } } @@ -363,7 +400,7 @@ sealed class Container { * Create the container using one or more `sender to recipient` asymmetric keys and a builder. See [create] * for builder usage sample. */ - fun createWith(plainData: UByteArray, vararg keys: Pair) = + fun createWith(plainData: UByteArray, vararg keys: AsymmetricEncryptionPair) = create(plainData) { key(*keys) } fun decode(encoded: UByteArray): Container { diff --git a/src/commonTest/kotlin/ContainerTest.kt b/src/commonTest/kotlin/ContainerTest.kt index fc00f85..960d3a1 100644 --- a/src/commonTest/kotlin/ContainerTest.kt +++ b/src/commonTest/kotlin/ContainerTest.kt @@ -1,5 +1,7 @@ import com.ionspin.kotlin.crypto.util.encodeToUByteArray import kotlinx.coroutines.test.runTest +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import net.sergeych.crypto2.* import kotlin.test.* @@ -12,9 +14,9 @@ class ContainerTest { val data = "sergeych, ohm many.".encodeToUByteArray() val c = Container.createWith(data, syk1) - assertFalse { c.isDecrypted } + assertTrue { c.isDecrypted } val c1 = Container.decode(c.encoded) - assertFalse { c.isDecrypted } + assertFalse { c1.isDecrypted } assertIs(c) assertNull(c1.decryptWith(syk2)) @@ -33,9 +35,9 @@ class ContainerTest { val data = "sergeych, ohm many.".encodeToUByteArray() val c = Container.createWith(data, p1.secretKey to p2.publicKey) - assertFalse { c.isDecrypted } + assertTrue { c.isDecrypted } val c1 = Container.decode(c.encoded) - assertFalse { c.isDecrypted } + assertFalse { c1.isDecrypted } assertIs(c) assertNull(c1.decryptWith(p3.secretKey)) @@ -54,9 +56,10 @@ class ContainerTest { val data = "sergeych, ohm many.".encodeToUByteArray() val c = Container.create(data) { key(p2.publicKey) } - assertFalse { c.isDecrypted } + assertTrue { c.isDecrypted } val c1 = Container.decode(c.encoded) - assertFalse { c.isDecrypted } + println(Json.encodeToString(c1)) + assertFalse { c1.isDecrypted } assertIs(c) assertNull(c1.decryptWith(p3.secretKey))