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

This commit is contained in:
sergeych committed 2024-06-24 15:44:35 +07:00
1 parent 5c97e857fc
commit 46a8b5bc06
12 files changed
+103 -184

No files matched your search

@@ -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.
@@ -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)
@@ -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}")
}
@@ -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)
}
@@ -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.
*/
@@ -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 ?:
@@ -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
@@ -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)
@@ -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) } )
}
}
@@ -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())