keys refactoring: removed separate UniversalKey, it is now a direct base of any real keys instead of deleted KeyInstance

This commit is contained in:
Sergey Chernov 2024-06-24 15:44:35 +07:00
parent 5c97e857fc
commit 46a8b5bc06
12 changed files with 103 additions and 184 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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}")
}

View File

@ -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)
}

View File

@ -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.
*/

View File

@ -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 ?:

View File

@ -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

View File

@ -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)

View File

@ -17,7 +17,7 @@ import kotlinx.serialization.Serializable
class UniversalRing(
val keyWithTags: Map<UniversalKey, Set<String>>,
) {
constructor(vararg keys: UniversalKey) : this(keys.associateWith { setOf() })
constructor(vararg keys: UniversalKey) : this(keys.associate { it to setOf() })
constructor(vararg keyTags: Pair<UniversalKey, String>)
: this(keyTags.associate { it.first to setOf(it.second) })
@ -37,10 +37,12 @@ class UniversalRing(
val allKeys: Set<UniversalKey> 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<KeyInstance,String>): UniversalRing =
UniversalRing(keyTags.associate { UniversalKey.from(it.first) to setOf(it.second) } )
fun from(vararg keyTags: Pair<UniversalKey,String>): UniversalRing =
UniversalRing(keyTags.associate {it.first to setOf(it.second) } )
}
}

View File

@ -0,0 +1,10 @@
package net.sergeych.tools
import net.sergeych.bipack.BipackDecoder
import net.sergeych.bipack.BipackEncoder
inline fun <reified T>bipack(value: T): UByteArray = BipackEncoder.encode(value).toUByteArray()
inline fun <reified T>biunpack(value: UByteArray): T = BipackDecoder.decode(value.toByteArray())

View File

@ -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<UniversalKey>(d) as SymmetricKey
assertEquals(k, k1)
}
}

View File

@ -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<EncryptingKey>(sk.id))
println(sk)
assertTrue { sk.publicKey.toUniversalKey() == r1.findKey<EncryptingKey>(sk.id) }
assertTrue { sk.publicKey.toUniversalKey() == r1.keyByTag<EncryptingKey>("foo") }
assertTrue { sk.publicKey as UniversalKey == r1.findKey<EncryptingKey>(sk.id) }
assertTrue { sk.publicKey as UniversalKey == r1.keyByTag<EncryptingKey>("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<EncryptingKey>(sk3.id))
assertTrue { sik3.publicKey in r }
assertTrue { sik3.verifyingKey in r }
assertTrue { sik1 in r }
assertEquals(sik1, r.keyByTag<UniversalKey>("SECRET_SIGN"))
assertEquals(sik1, r.findKey<SigningKey>(sik1.publicKey.id))
assertEquals(sik1, r.findKey<SigningKey>(sik1.verifyingKey.id))
}
@Test
@ -154,17 +154,17 @@ class RingTest {
var r1 = ra + rb + rc + rd
assertEquals(a, r1.findKey<UniversalKey.Secret>(a.id))
assertEquals(a, r1.findKey<Asymmetric.SecretKey>(a.id))
assertEquals(a, r1.keyByTag<UniversalKey>("foo_a"))
assertEquals(b, r1.findKey<SigningKey>(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<UniversalKey.Secret>(a.id))
assertEquals(a, r1.findKey<Asymmetric.SecretKey>(a.id))
assertEquals(a, r1.keyByTag<UniversalKey>("foo_a"))
assertEquals(b, r1.findKey<SigningKey>(b.id))
assertEquals(c, r1.keysById(c.key.id).first())
assertEquals(c, r1.keysById(c.id).first())
}
}