diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/Asymmetric.kt b/src/commonMain/kotlin/net/sergeych/crypto2/Asymmetric.kt index 1d40f60..81f3f1b 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/Asymmetric.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/Asymmetric.kt @@ -4,6 +4,7 @@ import com.ionspin.kotlin.crypto.box.Box import com.ionspin.kotlin.crypto.box.BoxCorruptedOrTamperedDataException import com.ionspin.kotlin.crypto.box.crypto_box_NONCEBYTES import com.ionspin.kotlin.crypto.scalarmult.ScalarMultiplication +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import net.sergeych.bipack.BipackDecoder @@ -121,7 +122,8 @@ object Asymmetric { * Anonymous encryption is very slow in comparison. */ @Serializable - class PublicKey(override val keyBytes: UByteArray) : BinaryKeyBase(), KeyInstance { + @SerialName("encp") + class PublicKey(override val keyBytes: UByteArray) : UniversalKey(), EncryptingKey { override val magick: KeysMagickNumber = KeysMagickNumber.defaultAssymmetric @@ -143,9 +145,14 @@ object Asymmetric { * Anonymous encryption, see [encryptAnonymousMessage], to binary data. Sender could not be identified. */ @Suppress("unused") - fun encrypt(plainData: UByteArray, randomFill: IntRange? = null): UByteArray = + override fun encrypt(plainData: UByteArray, randomFill: IntRange?): UByteArray = encryptMessage(plainData, randomFill = randomFill).encoded + override fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange?): UByteArray = + encryptMessage(plainData, nonce = nonce, randomFill = randomFill).encoded + + override val nonceBytesLength: Int = Asymmetric.nonceBytesLength + /** * Universal public-key encryption. Note that message authenticity is guaranteed if the decryption is successful * whether [senderKey] is provider, the latter only allow to positively identify the sender. @@ -173,19 +180,18 @@ object Asymmetric { randomFill: IntRange? = null, ): Message = createMessage(senderKey, this, WithFill.encode(plainData, randomFill)) - - fun toUniversalKey(): UniversalKey.Public = UniversalKey.Public(this) } /** * The secret key */ @Serializable + @SerialName("encs") class SecretKey( override val keyBytes: UByteArray, @Transient val _cachedPublicKey: PublicKey? = null, - ) : DecryptingKey, BinaryKeyBase() { + ) : DecryptingKey, UniversalKey() { /** * Decrypt with authentication checks the message which must have [Message.senderPublicKey] set. diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/BinaryKeyBase.kt b/src/commonMain/kotlin/net/sergeych/crypto2/BinaryKeyBase.kt deleted file mode 100644 index 8ee950b..0000000 --- a/src/commonMain/kotlin/net/sergeych/crypto2/BinaryKeyBase.kt +++ /dev/null @@ -1,31 +0,0 @@ -package net.sergeych.crypto2 - -import kotlinx.serialization.Serializable - -@Serializable -abstract class BinaryKeyBase() { - - abstract val keyBytes: UByteArray - abstract val magick: KeysMagickNumber - - open val id by lazy { KeyId(magick, keyBytes) } - - override fun equals(other: Any?): Boolean { - return other is BinaryKeyBase && other.keyBytes contentEquals keyBytes - } - - override fun hashCode(): Int { - return keyBytes.contentHashCode() - } - - override fun toString(): String = keyBytes.encodeToBase64Url() - - companion object { - - } -} - -open class IllegalSignatureException(text: String = "signed data is tampered or signature is corrupted") : - IllegalStateException(text) - -class ExpiredSignatureException(text: String) : IllegalSignatureException(text) \ No newline at end of file diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt b/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt index 16ae69b..252e49e 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt @@ -191,10 +191,6 @@ sealed class Container { key(k) } - is UniversalKey.Secret -> { - key(k.key.publicKey) - } - else -> { throw IllegalStateException("unknown key type to convert container: ${k::class.simpleName}") } diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/KeyInstance.kt b/src/commonMain/kotlin/net/sergeych/crypto2/KeyInstance.kt index e393042..8900c08 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/KeyInstance.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/KeyInstance.kt @@ -1,14 +1,12 @@ package net.sergeych.crypto2 +/** + * Marker interface: anything that is a key of some sort and can be identified + * with [KeyId]. + * + * Note that to be keyring-capable it should inherit from [UniversalKey] + */ interface KeyInstance { val id: KeyId } -/** - * Create a new instance of the corresponding key. - */ -@Suppress("unused") -fun KeyInstance.toUniversalKey(): UniversalKey { - if (this is UniversalKey) return this - return UniversalKey.from(this) -} diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/SigningPublicKey.kt b/src/commonMain/kotlin/net/sergeych/crypto2/SigningPublicKey.kt index 679dcd0..11a0efb 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/SigningPublicKey.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/SigningPublicKey.kt @@ -2,6 +2,7 @@ package net.sergeych.crypto2 import com.ionspin.kotlin.crypto.signature.InvalidSignatureException import com.ionspin.kotlin.crypto.signature.Signature +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient @@ -9,7 +10,8 @@ import kotlinx.serialization.Transient * Public key to verify signatures only */ @Serializable -class SigningPublicKey(override val keyBytes: UByteArray) : BinaryKeyBase(), VerifyingKey { +@SerialName("sigb") +class SigningPublicKey(override val keyBytes: UByteArray) : UniversalKey(), VerifyingKey { /** * Verify the signature and return true if it is correct. */ diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/SigningSecretKey.kt b/src/commonMain/kotlin/net/sergeych/crypto2/SigningSecretKey.kt index 558edb3..c06f32a 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/SigningSecretKey.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/SigningSecretKey.kt @@ -2,6 +2,7 @@ package net.sergeych.crypto2 import com.ionspin.kotlin.crypto.signature.Signature import kotlinx.datetime.Instant +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import net.sergeych.utools.now @@ -10,11 +11,12 @@ import net.sergeych.utools.now * Secret key to sign only */ @Serializable +@SerialName("sigs") class SigningSecretKey( override val keyBytes: UByteArray, @Transient private var cachedPublicKey: SigningPublicKey?=null - ) : BinaryKeyBase(), SigningKey { + ) : UniversalKey(), SigningKey { override val verifyingKey: SigningPublicKey by lazy { cachedPublicKey ?: diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt b/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt index bf61570..2c42bea 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt @@ -3,6 +3,7 @@ package net.sergeych.crypto2 import com.ionspin.kotlin.crypto.secretbox.SecretBox import com.ionspin.kotlin.crypto.secretbox.crypto_secretbox_KEYBYTES import com.ionspin.kotlin.crypto.secretbox.crypto_secretbox_NONCEBYTES +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient @@ -18,11 +19,14 @@ import kotlinx.serialization.Transient * - Authentication: Poly1305 MAC */ @Serializable +@SerialName("sym") class SymmetricKey( - val keyBytes: UByteArray, + override val keyBytes: UByteArray, @Transient val pbkdfParams: PBKD.Params?=null -) : EncryptingKey, DecryptingKey { +) : EncryptingKey, DecryptingKey, UniversalKey() { + + override val magick: KeysMagickNumber = KeysMagickNumber.defaultSymmetric /** * @suppress diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/UniversalKey.kt b/src/commonMain/kotlin/net/sergeych/crypto2/UniversalKey.kt index 7531a89..28a3835 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/UniversalKey.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/UniversalKey.kt @@ -1,116 +1,35 @@ package net.sergeych.crypto2 -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient -/** - * Serializable implementation of the _any key_ conception. Allows serializing collections - * of different keys with arbitrary types, such as [UniversalRing]. - * - * To create an `UniversalKey` instance use [UniversalKey.from] or [KeyInstance] - */ @Serializable -sealed class UniversalKey { - /** - * Propagate real ley instance. Base class itself can't be a key instance, but - * has the same id. This allows requiring key instances in [UniversalRing]. - */ - abstract val id: KeyId +sealed class UniversalKey: KeyInstance { - @Serializable - @SerialName("sym") - data class Symmetric(val key: SymmetricKey) : UniversalKey(), EncryptingKey by key, DecryptingKey by key { - @Transient - override val id: KeyId = key.id + abstract val keyBytes: UByteArray + abstract val magick: KeysMagickNumber - @Transient - override val nonceBytesLength: Int = key.nonceBytesLength + override val id by lazy { KeyId(magick, keyBytes) } - override fun toString() = "U.Sym:$id" + override fun equals(other: Any?): Boolean { + return other is UniversalKey && other.keyBytes contentEquals keyBytes } - @Serializable - @SerialName("ssn") - data class Session(val key: SafeKeyExchange.SessionKey) : UniversalKey(), EncryptingKey by key, - DecryptingKey by key { - @Transient - override val id: KeyId = key.id - @Transient - override val nonceBytesLength: Int = key.nonceBytesLength - - override fun toString() = "U.Ssn:$id" + override fun hashCode(): Int { + return keyBytes.contentHashCode() } - @Serializable - @SerialName("sec") - data class Secret(val key: Asymmetric.SecretKey) : UniversalKey(), DecryptingKey by key { - - @Transient - val publicKey: Public = Public(key.publicKey) - - override val id: KeyId by lazy { key.id } - override fun toString() = "U.Sec:$id" - } - - @Serializable - @SerialName("pub") - data class Public(val key: Asymmetric.PublicKey) : UniversalKey(), EncryptingKey { - - override val id: KeyId by lazy { key.id } - - override fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange?): UByteArray = - key.encryptMessage(plainData, nonce, randomFill= randomFill).encoded - - override fun encrypt(plainData: UByteArray, randomFill: IntRange?): UByteArray = - key.encryptMessage(plainData, randomFill=randomFill).encoded - - override val nonceBytesLength: Int - get() = Asymmetric.nonceBytesLength - - override fun toString() = "U.Pub:$id" - } - - @Serializable - @SerialName("sig") - data class Signing(val key: SigningSecretKey) : UniversalKey(), SigningKey by key { - override val id: KeyId by lazy { key.id } - override fun toString() = "U.Sig:$id" - - /** - * [Verifying] key, e.g. [verifyingKey] wrapped in the [UniversalKey] variant. - */ - val publicKey by lazy { Verifying(verifyingKey) } - - } - - @Serializable - @SerialName("ver") - data class Verifying(val key: SigningPublicKey) : UniversalKey(), VerifyingKey by key { - override val id: KeyId by lazy { key.id } - override fun toString() = "U.Ver:$id" - } - - + override fun toString(): String = keyBytes.encodeToBase64Url() companion object { - fun from(key: KeyInstance): UniversalKey = - when (key) { - is UniversalKey -> key - is Asymmetric.SecretKey -> Secret(key) - is Asymmetric.PublicKey -> Public(key) - is SymmetricKey -> Symmetric(key) - is SafeKeyExchange.SessionKey -> Session(key) - else -> throw UnsupportedOperationException("can't create universal key from ${key::class.simpleName}") - } - - fun newSecretKey(): Secret = - Secret(Asymmetric.newSecretKey()) - fun newSigningKey(): Signing = - Signing(SigningSecretKey.new()) + fun newSecretKey() = Asymmetric.newSecretKey() + fun newSigningKey() = SigningSecretKey.new() @Suppress("unused") - fun newSymmetricKey(): Symmetric = - Symmetric(SymmetricKey.new()) - } + fun newSymmetricKey() = SymmetricKey.new() + } } + +open class IllegalSignatureException(text: String = "signed data is tampered or signature is corrupted") : + IllegalStateException(text) + +class ExpiredSignatureException(text: String) : IllegalSignatureException(text) \ No newline at end of file diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/UniversalRing.kt b/src/commonMain/kotlin/net/sergeych/crypto2/UniversalRing.kt index ab0a9bb..3690a3f 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/UniversalRing.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/UniversalRing.kt @@ -17,7 +17,7 @@ import kotlinx.serialization.Serializable class UniversalRing( val keyWithTags: Map>, ) { - constructor(vararg keys: UniversalKey) : this(keys.associateWith { setOf() }) + constructor(vararg keys: UniversalKey) : this(keys.associate { it to setOf() }) constructor(vararg keyTags: Pair) : this(keyTags.associate { it.first to setOf(it.second) }) @@ -37,10 +37,12 @@ class UniversalRing( val allKeys: Set by lazy { keyWithTags.keys } /** - * Find a key of the specified type that matches the id. __Important__ it is not possible to - * require [UniversalKey] as [T], it is not a [KeyInstance], while its descendants are, [UniversalKey.Secret], etc. - * You can freely use [UniversalKey] subtypes or general key interfaces, e.g. - * [EncryptingKey], [DecryptingKey], [SigningKey] and [VerifyingKey]. + * Find a key of the specified type that matches the id. In general, you require key implementations like + * [Asymmetric.SecretKey], [Asymmetric.PublicKey], [SigningPublicKey], [SigningSecretKey] and [SymmetricKey], + * or just key interfaces: [EncryptingKey], [DecryptingKey], [SigningKey] and [VerifyingKey]. + * + * Note that key interfaces are not serializable as for now, you should try to cast to a serializable + * base. * * Please avoid selecting parameters that make possible to pick more than one key, it will cause an exception. * @@ -227,15 +229,15 @@ class UniversalRing( * Convert any keys to [UniversalKey] and form a ring with it. * @throws UnsupportedOperationException if [UniversalKey] can't build an instance of the specified class. */ - fun from(vararg keys: KeyInstance): UniversalRing = - UniversalRing(keys.associate { UniversalKey.from(it) to setOf() } ) + fun from(vararg keys: DecryptingKey): UniversalRing = + UniversalRing(keys.associate { it as UniversalKey to setOf() } ) /** * Convert any keys to [UniversalKey] and form a ring with it. * @throws UnsupportedOperationException if [UniversalKey] can't build an instance of the specified class. */ - fun from(vararg keyTags: Pair): UniversalRing = - UniversalRing(keyTags.associate { UniversalKey.from(it.first) to setOf(it.second) } ) + fun from(vararg keyTags: Pair): UniversalRing = + UniversalRing(keyTags.associate {it.first to setOf(it.second) } ) } } diff --git a/src/commonMain/kotlin/net/sergeych/tools/pack_tools.kt b/src/commonMain/kotlin/net/sergeych/tools/pack_tools.kt new file mode 100644 index 0000000..b1ae126 --- /dev/null +++ b/src/commonMain/kotlin/net/sergeych/tools/pack_tools.kt @@ -0,0 +1,10 @@ +package net.sergeych.tools + +import net.sergeych.bipack.BipackDecoder +import net.sergeych.bipack.BipackEncoder + + +inline fun bipack(value: T): UByteArray = BipackEncoder.encode(value).toUByteArray() + +inline fun biunpack(value: UByteArray): T = BipackDecoder.decode(value.toByteArray()) + diff --git a/src/commonTest/kotlin/KeysTest.kt b/src/commonTest/kotlin/KeysTest.kt index dd37b80..f8b4820 100644 --- a/src/commonTest/kotlin/KeysTest.kt +++ b/src/commonTest/kotlin/KeysTest.kt @@ -4,6 +4,8 @@ import kotlinx.coroutines.test.runTest import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import net.sergeych.crypto2.* +import net.sergeych.tools.bipack +import net.sergeych.tools.biunpack import net.sergeych.utools.now import net.sergeych.utools.pack import net.sergeych.utools.unpack @@ -187,9 +189,9 @@ class KeysTest { assertEquals(sy1, deepCopy(sy1), "symmetric key should be equal to the restored copy") assertEquals(sy1.hashCode(), deepCopy(sy1).hashCode(), "hashcode of the restored symmetric key is wrong") - val usy1 = UniversalKey.from(sy1) - val usy2 = UniversalKey.from(sy2) - val usy3 = UniversalKey.from(sy3) + val usy1 = sy1 as UniversalKey + val usy2 = sy2 as UniversalKey + val usy3 = sy3 as UniversalKey assertEquals(usy1, usy2) assertEquals(usy2, usy1) @@ -203,10 +205,10 @@ class KeysTest { assertEquals(sk2, sk1) assertFalse { sk1 == sk3 } - var usk1 = UniversalKey.from(sk1) - var usk2 = UniversalKey.from(sk2) - var usk3 = UniversalKey.from(sk3) - val usk4 = UniversalKey.from(sy3) + var usk1 = sk1 as UniversalKey + var usk2 = sk2 as UniversalKey + var usk3 = sk3 as UniversalKey + val usk4 = sy3 as UniversalKey assertEquals(usk1, usk2) assertEquals(usk2, usk1) @@ -223,7 +225,7 @@ class KeysTest { usk2 = deepCopy(usk2) usk3 = deepCopy(usk3) - assertEquals(usk1.hashCode(),usk2.hashCode()) + assertEquals(usk1.hashCode(), usk2.hashCode()) assertEquals(usk1, usk2) assertEquals(usk2, usk1) @@ -239,4 +241,13 @@ class KeysTest { // usk1 and usk2 are equal so set with only one of should be the same assertEquals(a, setOf(usk1, usk3, usk4)) } + + @Test + fun testKiSerialization() = runTest { + initCrypto() + val k: UniversalKey = SymmetricKey.new() + val d = bipack(k) + val k1: UniversalKey = biunpack(d) as SymmetricKey + assertEquals(k, k1) + } } \ No newline at end of file diff --git a/src/commonTest/kotlin/RingTest.kt b/src/commonTest/kotlin/RingTest.kt index 237a75c..52340cd 100644 --- a/src/commonTest/kotlin/RingTest.kt +++ b/src/commonTest/kotlin/RingTest.kt @@ -20,14 +20,14 @@ class RingTest { val e2: Asymmetric.SecretKey = BipackDecoder.decode(BipackEncoder.encode(e1)) assertEquals(e1, e2) - val k1 = UniversalKey.from(SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray())) - val k11 = UniversalKey.from(SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray())) + val k1 = SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray()) as UniversalKey + val k11 = SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray()) as UniversalKey assertEquals(k1, k11) - val k2 = UniversalKey.from(Asymmetric.newSecretKey()) - val k3 = UniversalKey.from(Asymmetric.newSecretKey()) + val k2 = Asymmetric.newSecretKey() + val k3 = Asymmetric.newSecretKey() // val r = UniversalRing(k1, k2) // val r = UniversalRing(k1) @@ -40,7 +40,7 @@ class RingTest { val encoded = BipackEncoder.encode(r) println(encoded.toDump()) println(encoded.size) - assertTrue { encoded.size < 80 } + assertTrue { encoded.size < 82 } val r2: UniversalRing = BipackDecoder.decode(encoded) assertTrue { k2 in r2 } assertTrue { k1 in r2 } @@ -69,16 +69,16 @@ class RingTest { val r1 = deepCopy(r) println(r1.findKey(sk.id)) println(sk) - assertTrue { sk.publicKey.toUniversalKey() == r1.findKey(sk.id) } - assertTrue { sk.publicKey.toUniversalKey() == r1.keyByTag("foo") } + assertTrue { sk.publicKey as UniversalKey == r1.findKey(sk.id) } + assertTrue { sk.publicKey as UniversalKey == r1.keyByTag("foo") } } @Test fun testTags() = runTest { initCrypto() - val k1 = UniversalKey.from(SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray())) - val k2 = UniversalKey.from(Asymmetric.newSecretKey()) + val k1 = SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray()) as UniversalKey + val k2 = Asymmetric.newSecretKey() as UniversalKey val r1 = UniversalRing(k1, k2) var r2 = UniversalRing(deepCopy(k1), deepCopy(k2)) @@ -114,7 +114,7 @@ class RingTest { val data = "Mendeleev' table".encodeToUByteArray() - var r = UniversalRing(sk2, sk3, sk1.publicKey, sk3.publicKey, sik3.publicKey, sik1.publicKey) + var r = UniversalRing(sk2, sk3, sk1.publicKey, sk3.publicKey, sik3.verifyingKey, sik1.verifyingKey) r = r.addTags(sik1, "SECRET_SIGN") @@ -122,11 +122,11 @@ class RingTest { assertContentEquals(data, box.decryptWith(r)) assertEquals(sk3.publicKey, r.findKey(sk3.id)) - assertTrue { sik3.publicKey in r } + assertTrue { sik3.verifyingKey in r } assertTrue { sik1 in r } assertEquals(sik1, r.keyByTag("SECRET_SIGN")) - assertEquals(sik1, r.findKey(sik1.publicKey.id)) + assertEquals(sik1, r.findKey(sik1.verifyingKey.id)) } @Test @@ -154,17 +154,17 @@ class RingTest { var r1 = ra + rb + rc + rd - assertEquals(a, r1.findKey(a.id)) + assertEquals(a, r1.findKey(a.id)) assertEquals(a, r1.keyByTag("foo_a")) assertEquals(b, r1.findKey(b.id)) - assertEquals(c, r1.keysById(c.key.id).first()) + assertEquals(c, r1.keysById(c.id).first()) r1 = UniversalRing.join(listOf(ra, rb, rc, rd)) - assertEquals(a, r1.findKey(a.id)) + assertEquals(a, r1.findKey(a.id)) assertEquals(a, r1.keyByTag("foo_a")) assertEquals(b, r1.findKey(b.id)) - assertEquals(c, r1.keysById(c.key.id).first()) + assertEquals(c, r1.keysById(c.id).first()) } } \ No newline at end of file