refining keys model
This commit is contained in:
parent
c4accfcd91
commit
bb383b5457
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -4,52 +4,100 @@ 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> =
|
||||
keyWithTags.keys.mapNotNull { it as? T }.toSet()
|
||||
allKeys.mapNotNull { it as? T }.toSet()
|
||||
|
||||
val allKeys: Set<UniversalKey> by lazy { keyWithTags.keys }
|
||||
|
||||
inline fun <reified T> findKey(id: KeyId): UniversalKey? =
|
||||
keyWithTags.keys.find { it is T && it.id == id }
|
||||
allKeys.find { it is T && it.id == id }
|
||||
|
||||
fun allByAnyOfTags(vararg tags: String) = sequence {
|
||||
fun keysById(id: KeyId): List<UniversalKey> = allKeys.filter { it.id == id }
|
||||
|
||||
/**
|
||||
* 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()))
|
||||
|
||||
/**
|
||||
* 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 UniversalRing(keyWithTags + keyWithTags)
|
||||
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()))
|
||||
|
||||
/**
|
||||
* 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()))
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
@ -60,9 +108,15 @@ class UniversalRing(
|
||||
return UniversalRing(kt1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@ -72,27 +126,64 @@ class UniversalRing(
|
||||
return keyWithTags == other.keyWithTags
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return keyWithTags.hashCode()
|
||||
}
|
||||
|
||||
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
|
||||
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())
|
||||
|
||||
fun removeTags(key: UniversalKey, tags: Set<String>): UniversalRing {
|
||||
/**
|
||||
* 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]?.let {
|
||||
kt1[key] = it - tags
|
||||
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 UniversalRing(kt1)
|
||||
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() } )
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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())
|
||||
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user