refining keys model

This commit is contained in:
Sergey Chernov 2024-06-23 07:09:27 +07:00
parent c4accfcd91
commit bb383b5457
5 changed files with 187 additions and 47 deletions

View File

@ -80,7 +80,7 @@ sealed class Container {
* @return decrypted data or null if this ring contains no proper key for it
*/
fun decryptWith(vararg keys: DecryptingKey): UByteArray? =
decryptWith(UniversalRing(*keys))
decryptWith(UniversalRing.from(*keys))
@Transient
var decryptedData: UByteArray? = null

View File

@ -3,13 +3,28 @@ package net.sergeych.crypto2
import kotlinx.serialization.Serializable
/**
* Tag that identifies in some way the _decrypting key_. Is used in keyrings and
* containers to fast find a proper key
* Key Identity. Can be used to find matching keys for decryption or verifying, etc. The identity
* may contain [KDF] parameters if the corresponding key could be derived from a password.
* Note that [kdf] part is not respected in [equals].
*
* Important. `KeyId` of matching keys are the same, so you can use it to identify
* and find matching keys in the [UniversalRing], etc. For example:
*
* - [Asymmetric.SecretKey] and [Asymmetric.PublicKey] from the same pair have the same `KeyId`, thus the former
* can decrypt what was encrypted with the latter.
*
* - [SigningSecretKey] and corresponding [VerifyingKey] have the same `KeyId`. Use it to pick a proper key for
* signing from a ring with [UniversalRing.findKey]
*
*/
@Serializable
data class KeyId(val id: BinaryId, val kdp: KeyDerivationParams?=null ) {
data class KeyId(val id: BinaryId, val kdf: KeyDerivationParams?=null ) {
val tag: UByteArray by lazy { id.id }
/**
* Binary array representation of the [id], not including the [kdf]. Used in [SafeKeyExchange]
* and other key exchanges to generate session tokens, etc.
*/
val binaryTag: UByteArray by lazy { id.id }
override fun equals(other: Any?): Boolean {
if (this === other) return true

View File

@ -47,9 +47,9 @@ class SafeKeyExchange {
@Suppress("unused")
val sessionTag: UByteArray by lazy {
if (!isClient)
blake2b(id.tag + id.tag)
blake2b(id.binaryTag + id.binaryTag)
else
blake2b(id.tag + id.tag)
blake2b(id.binaryTag + id.binaryTag)
}
override val id: KeyId

View File

@ -4,53 +4,101 @@ import kotlinx.serialization.Serializable
@Serializable
class UniversalRing(
val keyWithTags: Map<UniversalKey,Set<String>>
val keyWithTags: Map<UniversalKey, Set<String>>,
) {
constructor(vararg keys: UniversalKey) : this(keys.associateWith { setOf() })
constructor(vararg keys: DecryptingKey) : this(keys.associate { UniversalKey.from(it) to setOf<String>() })
constructor(vararg keyTags: Pair<UniversalKey, String>)
: this(keyTags.associate { it.first to setOf(it.second) })
val decryptingKeys: Set<DecryptingKey> by lazy { keys<DecryptingKey>() }
/**
* Select keys of the specified type
*/
inline fun <reified T> keys(): Set<T> =
allKeys.mapNotNull { it as? T }.toSet()
inline fun <reified T>keys(): Set<T> =
keyWithTags.keys.mapNotNull { it as? T }.toSet()
val allKeys: Set<UniversalKey> by lazy { keyWithTags.keys }
inline fun <reified T> findKey(id: KeyId): UniversalKey? =
allKeys.find { it is T && it.id == id }
inline fun <reified T>findKey(id: KeyId): UniversalKey? =
keyWithTags.keys.find { it is T && it.id == id }
fun keysById(id: KeyId): List<UniversalKey> = allKeys.filter { it.id == id }
fun allByAnyOfTags(vararg tags: String) = sequence {
for( e in keyWithTags.entries) {
if( tags.any { it in e.value }) yield(e.key)
/**
* Return sequence of keys that have at least one of the [tags]
*/
fun keysByTags(vararg tags: String) = sequence {
for (e in keyWithTags.entries) {
if (tags.any { it in e.value }) yield(e.key)
}
}
inline fun <reified T>keyByTag(tag: String) = allByAnyOfTags(tag).first { it is T }
/**
* Get the first key of the specified type having the [tag]
*/
inline fun <reified T> keyByTag(tag: String) = keysByTags(tag).first { it is T }
@Suppress("unused")
inline fun <reified T>keyByAnyTag(vararg tags: String) = allByAnyOfTags(*tags).first { it is T }
inline fun <reified T> keyByAnyTag(vararg tags: String) = keysByTags(*tags).first { it is T }
operator fun get(keyId: KeyId): Collection<UniversalKey> = keyWithTags.keys.filter { it.id == keyId }
/**
* Get all keys with a given id. Note that _matching keys_ have the same id, see [KeyId] for more.
*/
operator fun get(keyId: KeyId): Collection<UniversalKey> = allKeys.filter { it.id == keyId }
fun getTags(key: UniversalKey): Set<String>? = keyWithTags[key]
operator fun contains(element: UniversalKey): Boolean = element in keyWithTags
/**
* Add a key if not already in a ring, otherwise return existing ring.
*/
operator fun plus(key: UniversalKey): UniversalRing =
if( key in this ) this else UniversalRing(keyWithTags + (key to setOf()) )
if (key in this) this else UniversalRing(keyWithTags + (key to setOf()))
operator fun plus(keyTag: Pair<UniversalKey,String>): UniversalRing =
if( keyTag.first in this )
addTags(keyTag.first,keyTag.second)
else UniversalRing(keyWithTags + keyWithTags)
/**
* Add a key and tags to the ring. If the key exists, add the tag to it.
* See also [addTags].
*/
operator fun plus(keyTag: Pair<UniversalKey, String>): UniversalRing =
if (keyTag.first in this)
addTags(keyTag.first, keyTag.second)
else add(keyTag.first, keyTag.second)
/**
* Merge two rings. The result will contain all keys and all tags from
* both rings.
*/
operator fun plus(other: UniversalRing): UniversalRing {
var result = keyWithTags.toMutableMap()
for (e in other.keyWithTags.entries) {
result[e.key]?.let {
result[e.key] = it + e.value
} ?: run {
result[e.key] = e.value
}
}
return UniversalRing(result)
}
/**
* Add key and tags to the ring. If the key already exists, tags are merged.
*/
fun add(key: UniversalKey, vararg tags: String): UniversalRing =
UniversalRing(keyWithTags + (key to tags.toSet()) )
UniversalRing(keyWithTags + (key to tags.toSet()))
/**
* Add key and tags to the ring. If the key already exists, tags are merged.
*/
fun add(key: UniversalKey, tags: Collection<String>): UniversalRing =
UniversalRing(keyWithTags + (key to tags.toSet()) )
UniversalRing(keyWithTags + (key to tags.toSet()))
fun addTags(key: UniversalKey,tags: Set<String>): UniversalRing {
/**
* Add tags to the key, and add key if it is not in the ring.
*/
fun addTags(key: UniversalKey, tags: Set<String>): UniversalRing {
val kt1 = keyWithTags.toMutableMap()
kt1[key]?.let {
kt1[key] = it + tags
@ -60,11 +108,17 @@ class UniversalRing(
return UniversalRing(kt1)
}
fun addTags(key: UniversalKey,vararg tags: String): UniversalRing =
addTags(key,tags.toSet())
/**
* Add tags to the key, and add key if it is not in the ring.
*/
fun addTags(key: UniversalKey, vararg tags: String): UniversalRing =
addTags(key, tags.toSet())
/**
* return the ring without [key] if existed, otherwise returns `this`.
*/
operator fun minus(key: UniversalKey): UniversalRing =
if( key in this ) UniversalRing(keyWithTags.filter { it.key != key }) else this
if (key in this) UniversalRing(keyWithTags.filter { it.key != key }) else this
override fun equals(other: Any?): Boolean {
if (this === other) return true
@ -72,27 +126,64 @@ class UniversalRing(
return keyWithTags == other.keyWithTags
}
override fun toString(): String {
val ss = keyWithTags.entries
.joinToString(","){"${it.value.joinToString{ ":" }}:${it.key}"}
return "Kr[$ss]"
}
override fun hashCode(): Int {
return keyWithTags.hashCode()
}
infix fun equalKeys(other: UniversalRing): Boolean = keyWithTags.keys == other.keyWithTags.keys
fun removeTags(key: UniversalKey, vararg tags: String): UniversalRing = removeTags(key, tags.toSet())
fun removeTags(key: UniversalKey, tags: Set<String>): UniversalRing {
val kt1 = keyWithTags.toMutableMap()
kt1[key]?.let {
kt1[key] = it - tags
}
return UniversalRing(kt1)
override fun toString(): String {
val ss = keyWithTags.entries
.joinToString(",") { "${it.value.joinToString { ":" }}:${it.key}" }
return "Kr[$ss]"
}
infix fun equalKeys(other: UniversalRing): Boolean = allKeys == other.allKeys
/**
* Create a collection where no [tags] are specified for the [key]. If the key is not
* in the collection, it returns this.
*/
fun removeTags(key: UniversalKey, vararg tags: String): UniversalRing = removeTags(key, tags.toSet())
/**
* Create a collection where no [tags] are specified for the [key]. If the key is not
* in the collection, it returns this.
*/
fun removeTags(key: UniversalKey, tags: Set<String>): UniversalRing =
keyWithTags[key]?.let { existingTags ->
val kt1 = keyWithTags.toMutableMap()
kt1[key] = existingTags - tags
UniversalRing(kt1)
} ?: this
/**
* Create string "report" of the ring contents. Note it has no trailing `\n`
*/
fun ls(): String {
val result = mutableListOf<String>()
for( e in keyWithTags.entries) {
result += "${e.key} ${e.value.joinToString(" ")}"
}
return result.joinToString("\n")
}
companion object {
val EMPTY = UniversalRing(keyWithTags = emptyMap())
/**
* Join a collection of keyrings together (same as reducing with `+`). Correctly
* works if there is no keyring (returns [EMPTY]), or only one keyring (returns
* it with no extra work).
*
* Note that for the known number of rings using [plus] is more clear and expressive
*/
fun join(keyRings: Collection<UniversalRing>): UniversalRing {
if (keyRings.isEmpty()) return EMPTY
if (keyRings.size == 1) return keyRings.first()
return keyRings.reduce { l, r -> l + r }
}
fun from(vararg keys: DecryptingKey): UniversalRing =
UniversalRing(keys.associate { UniversalKey.from(it) to setOf() } )
}
}

View File

@ -114,6 +114,40 @@ class RingTest {
@Test
fun testSize() = runTest {
// val sy1 = SymmetricKey.random().toUniversal()
initCrypto()
val r = UniversalRing(UniversalKey.newSymmetricKey())
val rd = BipackEncoder.encode(r)
println(rd.size)
println(rd.toDump())
assertTrue { rd.size <= 46 }
}
@Test
fun testJoin() = runTest {
initCrypto()
val a = UniversalKey.newSecretKey()
val b = UniversalKey.newSigningKey()
val c = UniversalKey.newSymmetricKey()
val d = UniversalKey.newSigningKey()
val ra = UniversalRing(a)
val rb = UniversalRing(b)
val rc = UniversalRing(c)
val rd = UniversalRing(d) + (a to "foo_a")
var r1 = ra + rb + rc + rd
assertEquals(a, r1.findKey<UniversalKey.Secret>(a.id))
assertEquals(a, r1.keyByTag<UniversalKey>("foo_a"))
assertEquals(b, r1.findKey<SigningKey>(b.id))
assertEquals(c, r1.keysById(c.key.id).first())
r1 = UniversalRing.join(listOf(ra, rb, rc, rd))
assertEquals(a, r1.findKey<UniversalKey.Secret>(a.id))
assertEquals(a, r1.keyByTag<UniversalKey>("foo_a"))
assertEquals(b, r1.findKey<SigningKey>(b.id))
assertEquals(c, r1.keysById(c.key.id).first())
}
}