diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt b/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt index 8472951..00cad99 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt @@ -55,6 +55,47 @@ sealed class Container { */ val isDecrypted: Boolean get() = decryptedData != null + /** + * Add one or more recipients to the __decrypted__ container using a standard builder. Note that + * [Builder.fill] is not working in this case. + */ + fun addRecipients(builder: Builder.() -> Unit): Container = + if (this is Single) asOpenMulti.addRecipients(builder) + else { + Builder(this).apply(builder).build() + } + + /** + * 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. The new container is also decrypted so you can add + * more keys, etc. + */ + operator fun plus(recipient: EncryptingKey) = addRecipients { key(recipient) } + + /** + * 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) } + + abstract fun updateData(newPlainData: UByteArray,randomFill: IntRange?=null): Container + + /** + * Binary encoded version. It is desirable to include [Container] as an object, though, + * especially when using custom serialization (Json, Boss, etc), it is serializable. + * Still, if you need it in binary form, this is a shortcut. You can use [decode] or call + * [BipackDecoder.decode] to deserialize the binary form. + */ + val encoded: UByteArray by lazy { + BipackEncoder.encode(this).toUByteArray() + } + + /** * @suppress * Single-key variant, to conserve space it does not use the main key logic and just encrypts the data. @@ -89,10 +130,10 @@ sealed class Container { return null } - internal val asOpenMulti: Container by lazy { + private fun reEncrypt(data: UByteArray? = null,f: Builder.()->Unit): Container { check(isDecrypted) { "container should be decrypted" } - create(decryptedData!!) { - asOpenedMulti() + return create(data ?: decryptedData ?: throw IllegalArgumentException("no data is provided")) { + f() // The encryption key is known if we just created the container if (reEncryptionKey != null) key(reEncryptionKey) @@ -120,7 +161,17 @@ sealed class Container { } } } + } + + internal val asOpenMulti: Container by lazy { + reEncrypt { alwaysMulti() } + } + + override fun updateData(newPlainData: UByteArray,randomFill: IntRange?): Container = + reEncrypt(newPlainData) { + randomFill?.let { fill(it) } + } } /** @@ -133,7 +184,6 @@ sealed class Container { 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) { @@ -176,44 +226,11 @@ sealed class Container { } return null } - } - /** - * Add one or more recipients to the __decrypted__ container using a standard builder. Note that - * [Builder.fill] is not working in this case. - */ - fun addRecipients(builder: Builder.() -> Unit): Container = - if (this is Single) asOpenMulti.addRecipients(builder) - else { - Builder(this).apply(builder).build() - } - - /** - * 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. The new container is also decrypted so you can add - * more keys, etc. - */ - operator fun plus(recipient: EncryptingKey) = addRecipients { key(recipient) } - - /** - * 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) } - - /** - * Binary encoded version. It is desirable to include [Container] as an object, though, - * especially when using custom serialization (Json, Boss, etc), it is serializable. - * Still, if you need it in binary form, this is a shortcut. You can use [decode] or call - * [BipackDecoder.decode] to deserialize the binary form. - */ - val encoded: UByteArray by lazy { - BipackEncoder.encode(this).toUByteArray() + override fun updateData(newPlainData: UByteArray, randomFill: IntRange?): Container = + Builder(newPlainData, this).apply { + randomFill?.let { fill(it) } + }.build() } companion object { @@ -286,14 +303,14 @@ sealed class Container { fillRange = range } - private var makeOpenedMulti = false + private var alwaysMulti = false /** * @suppress * will produce multikey internal variant even with only one key. User internally */ - internal fun asOpenedMulti() { - makeOpenedMulti = true + internal fun alwaysMulti() { + alwaysMulti = true } /** @@ -303,8 +320,8 @@ sealed class Container { val countNewKeys = plainKeys.size + keyPairs.size 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 && makeOpenedMulti == false && parent == null -> { + countNewKeys == 0 && parent == null -> throw IllegalArgumentException("Container needs at least one key") + countNewKeys == 1 && alwaysMulti == false && parent == null -> { createSingle() } @@ -354,10 +371,7 @@ sealed class Container { val (sender, recipient) = p eks += Multi.EncryptedKey(sender, recipient, encodedMainKey) } - return if (makeOpenedMulti) - Multi(eks, mainKey.encrypt(plainData, fillRange), mainKey, plainData) - else - Multi(eks, mainKey.encrypt(plainData, fillRange)) + return Multi(eks, mainKey.encrypt(plainData, fillRange), mainKey, plainData) } } diff --git a/src/commonTest/kotlin/ContainerTest.kt b/src/commonTest/kotlin/ContainerTest.kt index 960d3a1..383b5ad 100644 --- a/src/commonTest/kotlin/ContainerTest.kt +++ b/src/commonTest/kotlin/ContainerTest.kt @@ -24,6 +24,11 @@ class ContainerTest { assertNotNull(d) assertContentEquals(data, d) assertTrue { c1.isDecrypted } + + val data2 = "To push unpushinable".encodeToUByteArray() + val c2 = Container.decode(c.updateData(data2).encoded) + assertFalse { c2.isDecrypted } + assertContentEquals(data2, c2.decryptWith(syk1)) } @Test @@ -45,6 +50,11 @@ class ContainerTest { assertNotNull(d) assertContentEquals(data, d) assertTrue { c1.isDecrypted } + + val data2 = "To push unpushinable".encodeToUByteArray() + val c2 = Container.decode(c.updateData(data2).encoded) + assertFalse { c2.isDecrypted } + assertContentEquals(data2, c2.decryptWith(p2.secretKey)) } @Test @@ -67,6 +77,11 @@ class ContainerTest { assertNotNull(d) assertContentEquals(data, d) assertTrue { c1.isDecrypted } + + val data2 = "To push unpushinable".encodeToUByteArray() + val c2 = Container.decode(c.updateData(data2).encoded) + assertFalse { c2.isDecrypted } + assertContentEquals(data2, c2.decryptWith(p2.secretKey)) } @Test @@ -86,7 +101,7 @@ class ContainerTest { key(p1.secretKey to p3.publicKey) key(p4.publicKey) } - assertFalse { c.isDecrypted } + assertTrue { c.isDecrypted } var c1 = Container.decode(c.encoded) assertFalse { c1.isDecrypted } @@ -104,6 +119,13 @@ class ContainerTest { c1 = Container.decode(c.encoded) assertFalse { c1.isDecrypted } assertContentEquals(data, c1.decryptWith(syk3, p4.secretKey)) + + val data2 = "To push unpushinable".encodeToUByteArray() + assertTrue { c.isDecrypted } + val c2 = Container.decode(c.updateData(data2).encoded) + assertFalse { c2.isDecrypted } + assertContentEquals(data2, c2.decryptWith(syk2)) + } @Test @@ -119,6 +141,7 @@ class ContainerTest { .encodeToUByteArray() var c = Container.createWith(data, syk1) + assertTrue { c.isDecrypted } fun expectOpen(k: DecryptingKey) { val c1 = Container.decode(c.encoded) @@ -158,6 +181,13 @@ class ContainerTest { expectOpen(p3.secretKey) expectNotOpen(syk3) expectOpen(p4.secretKey) + + val data2 = "To push unpushinable".encodeToUByteArray() + assertTrue { c.isDecrypted } + val c2 = Container.decode(c.updateData(data2).encoded) + assertFalse { c2.isDecrypted } + assertContentEquals(data2, c2.decryptWith(p4.secretKey)) + } @Test @@ -195,4 +225,80 @@ class ContainerTest { expectNotOpen(syk2) expectOpen(p3.secretKey) } + + @Test + fun testMixedOps1() = runTest { + initCrypto() + val syk1 = SymmetricKey.random() + val syk2 = SymmetricKey.random() + val syk3 = SymmetricKey.random() + val p1 = Asymmetric.generateKeys() + val p2 = Asymmetric.generateKeys() + val p3 = Asymmetric.generateKeys() + val p4 = Asymmetric.generateKeys() + val data = "Translating the name 'Sergey Chernov' from Russian to archaic Sanskrit would be 'Ramo Krishna'" + .encodeToUByteArray() + + var c = Container.createWith(data, p1.secretKey to p3.publicKey) + + fun expectOpen(testData: UByteArray,vararg keys: DecryptingKey) { + val c1 = Container.decode(c.encoded) + for( k in keys) + assertContentEquals(testData, c1.decryptWith(k)) + } + + fun expectNotOpen(vararg keys: DecryptingKey) { + val c1 = Container.decode(c.encoded) + for(k in keys) assertNull(c1.decryptWith(k)) + } + + expectNotOpen(syk1) + expectOpen(data, p3.secretKey) + +// c = Container.decode(c.encoded) + + val data2 = "Cocktails have a delicious, complex taste".encodeToUByteArray() + c = (c + syk3 + (p1.secretKey to p4.publicKey)).updateData(data2) + expectNotOpen(syk2, syk1, p1.secretKey, p2.secretKey ) + expectOpen(data2, syk3, p3.secretKey, p4.secretKey) + } + + @Test + fun testMixedOps2() = runTest { + initCrypto() + val syk1 = SymmetricKey.random() + val syk2 = SymmetricKey.random() + val syk3 = SymmetricKey.random() + val p1 = Asymmetric.generateKeys() + val p2 = Asymmetric.generateKeys() + val p3 = Asymmetric.generateKeys() + val p4 = Asymmetric.generateKeys() + val data = "Translating the name 'Sergey Chernov' from Russian to archaic Sanskrit would be 'Ramo Krishna'" + .encodeToUByteArray() + + var c = Container.createWith(data, p1.secretKey to p3.publicKey) + + fun expectOpen(testData: UByteArray,vararg keys: DecryptingKey) { + val c1 = Container.decode(c.encoded) + for( k in keys) + assertContentEquals(testData, c1.decryptWith(k)) + } + + fun expectNotOpen(vararg keys: DecryptingKey) { + val c1 = Container.decode(c.encoded) + for(k in keys) assertNull(c1.decryptWith(k)) + } + + expectNotOpen(syk1) + expectOpen(data, p3.secretKey) + + c = Container.decode(c.encoded) + c.decryptWith(p3.secretKey) + + val data2 = "Cocktails have a delicious, complex taste".encodeToUByteArray() + c = (c + syk3 + (p1.secretKey to p4.publicKey)).updateData(data2) + expectNotOpen(syk2, syk1, p1.secretKey, p2.secretKey ) + expectOpen(data2, syk3, p3.secretKey, p4.secretKey) + } + } \ No newline at end of file