refactored keys, unversal keys and ring

This commit is contained in:
Sergey Chernov 2024-06-22 18:18:14 +07:00
parent 7eeb780bdb
commit ede55650f4
16 changed files with 274 additions and 241 deletions

View File

@ -109,7 +109,7 @@ object Asymmetric {
private fun randomNonce(): UByteArray = randomUBytes(crypto_box_NONCEBYTES) private fun randomNonce(): UByteArray = randomUBytes(crypto_box_NONCEBYTES)
fun randomSecretKey(): SecretKey = generateKeys().secretKey fun new(): SecretKey = generateKeys().secretKey
@Suppress("unused") @Suppress("unused")
val nonceBytesLength = crypto_box_NONCEBYTES val nonceBytesLength = crypto_box_NONCEBYTES
@ -121,22 +121,9 @@ object Asymmetric {
* Anonymous encryption is very slow in comparison. * Anonymous encryption is very slow in comparison.
*/ */
@Serializable @Serializable
class PublicKey(val keyBytes: UByteArray) { class PublicKey(override val keyBytes: UByteArray) : BinaryKeyBase() {
val tag: KeyId by lazy { override val magick: KeysMagickNumber = KeysMagickNumber.defaultAssymmetric
KeyId(KeysMagickNumbers.defaultAssymmetric, blake2b(keyBytes))
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is PublicKey) return false
return keyBytes contentEquals other.keyBytes
}
override fun hashCode(): Int {
return keyBytes.contentHashCode()
}
/** /**
* Create an anonymous message that could be decrypted only with the [SecretKey] that corresponds this. * Create an anonymous message that could be decrypted only with the [SecretKey] that corresponds this.
@ -171,7 +158,7 @@ object Asymmetric {
fun encryptMessage( fun encryptMessage(
plainData: UByteArray, plainData: UByteArray,
nonce: UByteArray = randomNonce(), nonce: UByteArray = randomNonce(),
senderKey: SecretKey = randomSecretKey(), senderKey: SecretKey = new(),
randomFill: IntRange? = null, randomFill: IntRange? = null,
) = createMessage(senderKey, this, WithFill.encode(plainData, randomFill), nonce) ) = createMessage(senderKey, this, WithFill.encode(plainData, randomFill), nonce)
@ -192,10 +179,10 @@ object Asymmetric {
*/ */
@Serializable @Serializable
class SecretKey( class SecretKey(
val keyBytes: UByteArray, override val keyBytes: UByteArray,
@Transient @Transient
val _cachedPublicKey: PublicKey? = null, val _cachedPublicKey: PublicKey? = null,
) : DecryptingKey { ) : DecryptingKey, BinaryKeyBase() {
/** /**
* Decrypt with authentication checks the message which must have [Message.senderPublicKey] set. * Decrypt with authentication checks the message which must have [Message.senderPublicKey] set.
@ -226,17 +213,6 @@ object Asymmetric {
.also { cachedPublicKey = it } .also { cachedPublicKey = it }
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is SecretKey) return false
return keyBytes contentEquals other.keyBytes
}
override fun hashCode(): Int {
return keyBytes.contentHashCode()
}
/** /**
* Nonce-based decryption is impossible, it is already included in message * Nonce-based decryption is impossible, it is already included in message
*/ */
@ -250,7 +226,8 @@ object Asymmetric {
return message.decrypt(this) return message.decrypt(this)
} }
override val id: KeyId by lazy { publicKey.tag } override val magick: KeysMagickNumber = KeysMagickNumber.defaultAssymmetric
override val id: KeyId by lazy { publicKey.id }
override val nonceBytesLength: Int override val nonceBytesLength: Int
get() = 0 get() = 0

View File

@ -0,0 +1,47 @@
package net.sergeych.crypto2
import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable
interface VerifyingKey {
val id: KeyId
/**
* Verify the signature and return true if it is correct.
*/
fun verify(signature: UByteArray, message: UByteArray): Boolean
}
interface SigningKey {
val verifyingKey: SigningPublicKey
fun sign(message: UByteArray): UByteArray
fun seal(message: UByteArray, expiresAt: Instant? = null): Seal
}
@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

@ -232,10 +232,10 @@ sealed class Container {
constructor(sender: Asymmetric.SecretKey?, recipient: Asymmetric.PublicKey, encodeMainKey: UByteArray) : constructor(sender: Asymmetric.SecretKey?, recipient: Asymmetric.PublicKey, encodeMainKey: UByteArray) :
this( this(
recipient.tag, recipient.id,
recipient.encryptMessage( recipient.encryptMessage(
encodeMainKey, encodeMainKey,
senderKey = sender ?: Asymmetric.randomSecretKey(), senderKey = sender ?: Asymmetric.new(),
).encoded ).encoded
) )
} }
@ -375,7 +375,7 @@ sealed class Container {
mainKey = p.mainKey ?: throw IllegalStateException("parent container must be decrypted") mainKey = p.mainKey ?: throw IllegalStateException("parent container must be decrypted")
} else { } else {
eks = mutableListOf<Multi.EncryptedKey>() eks = mutableListOf<Multi.EncryptedKey>()
mainKey = SymmetricKey.random() mainKey = SymmetricKey.new()
} }
val encodedMainKey = BipackEncoder.encode(mainKey).toUByteArray() val encodedMainKey = BipackEncoder.encode(mainKey).toUByteArray()
createMulti(eks, encodedMainKey, mainKey) createMulti(eks, encodedMainKey, mainKey)
@ -389,9 +389,9 @@ sealed class Container {
val pair = keyPairs.first() val pair = keyPairs.first()
val (sk, pk) = pair val (sk, pk) = pair
Single( Single(
pk.tag, pk.encryptMessage( pk.id, pk.encryptMessage(
plainData, plainData,
senderKey = sk ?: Asymmetric.randomSecretKey(), senderKey = sk ?: Asymmetric.new(),
randomFill = fillRange randomFill = fillRange
).encoded, ).encoded,
plainData, plainData,

View File

@ -23,6 +23,6 @@ data class KeyId(val id: BinaryId, val kdp: KeyDerivationParams?=null ) {
override fun toString() = id.toString() override fun toString() = id.toString()
constructor(magickNumber: KeysMagickNumbers, data: UByteArray,kdp: KeyDerivationParams?=null) constructor(magickNumber: KeysMagickNumber, data: UByteArray, kdp: KeyDerivationParams?=null)
: this(BinaryId.createFromUBytes(magickNumber.number, data), kdp) : this(BinaryId.createFromUBytes(magickNumber.number, data), kdp)
} }

View File

@ -1,10 +1,11 @@
package net.sergeych.crypto2 package net.sergeych.crypto2
enum class KeysMagickNumbers(val number: Int) { enum class KeysMagickNumber(val number: Int) {
defaultAssymmetric(0), defaultAssymmetric(0),
defaultSymmetric(1), defaultSymmetric(1),
defaultSession(2), defaultSession(2),
defaultSigning(3), defaultSigning(3),
defaultVerifying(4),
; ;
companion object { companion object {
@ -12,6 +13,6 @@ enum class KeysMagickNumbers(val number: Int) {
val forNumber = entries.map { it.number to it }.toMap() val forNumber = entries.map { it.number to it }.toMap()
@Suppress("unused") @Suppress("unused")
fun findFor(binaryId: BinaryId): KeysMagickNumbers? = forNumber[binaryId.magick] fun findFor(binaryId: BinaryId): KeysMagickNumber? = forNumber[binaryId.magick]
} }
} }

View File

@ -23,7 +23,7 @@ import net.sergeych.utools.now
*/ */
@Serializable @Serializable
class Seal( class Seal(
val publicKey: Signing.PublicKey, val publicKey: SigningPublicKey,
val signature: UByteArray, val signature: UByteArray,
val nonce: UByteArray?, val nonce: UByteArray?,
val createdAt: Instant, val createdAt: Instant,
@ -95,7 +95,7 @@ class Seal(
* Seal [message] with a [key]. * Seal [message] with a [key].
* *
* Seals are kotlinx-serializable and can be used * Seals are kotlinx-serializable and can be used
* to check the authenticity of the arbitrary [message] using a public key, [Signing.PublicKey] * to check the authenticity of the arbitrary [message] using a public key, [SigningPublicKey]
* instance, using public-key signing algorithms. * instance, using public-key signing algorithms.
* *
* Unlike a regular binary signature, Seal contains the signer's [publicKey], and also * Unlike a regular binary signature, Seal contains the signer's [publicKey], and also
@ -130,7 +130,7 @@ class Seal(
* rare case so default os false. * rare case so default os false.
*/ */
fun create( fun create(
key: Signing.SecretKey, key: SigningSecretKey,
message: UByteArray, message: UByteArray,
createdAt: Instant = now(), createdAt: Instant = now(),
expiresAt: Instant? = null, expiresAt: Instant? = null,
@ -138,7 +138,7 @@ class Seal(
): Seal { ): Seal {
val nonce = if( nonDeterministic ) randomUBytes(32) else null val nonce = if( nonDeterministic ) randomUBytes(32) else null
val data = BipackEncoder.encode(SealedData(message, nonce, createdAt, expiresAt)).toUByteArray() val data = BipackEncoder.encode(SealedData(message, nonce, createdAt, expiresAt)).toUByteArray()
return Seal(key.publicKey, key.sign(data), nonce, createdAt, expiresAt) return Seal(key.verifyingKey, key.sign(data), nonce, createdAt, expiresAt)
} }
/** /**

View File

@ -30,7 +30,7 @@ class SealedBox(
) { ) {
@Suppress("unused") @Suppress("unused")
constructor(message: UByteArray, vararg keys: Signing.SecretKey) : constructor(message: UByteArray, vararg keys: SigningSecretKey) :
this(message, keys.map { it.seal(message) } ) this(message, keys.map { it.seal(message) } )
/** /**
@ -38,23 +38,23 @@ class SealedBox(
* key, or return unchanged (same) object if it is already signed by this key; you * key, or return unchanged (same) object if it is already signed by this key; you
* _can't assume it always returns a copied object!_ * _can't assume it always returns a copied object!_
*/ */
operator fun plus(key: Signing.SecretKey): SealedBox = operator fun plus(key: SigningSecretKey): SealedBox =
if (key.publicKey in this) this if (key.verifyingKey in this) this
else SealedBox(message, seals + key.seal(message),false) else SealedBox(message, seals + key.seal(message),false)
/** /**
* Add expiring seal, otherwise use [plus]. Overrides exising seal for [key] * Add expiring seal, otherwise use [plus]. Overrides exising seal for [key]
* if present: * if present:
*/ */
fun addSeal(key: Signing.SecretKey, expresAt: Instant): SealedBox { fun addSeal(key: SigningSecretKey, expresAt: Instant): SealedBox {
val filtered = seals.filter { it.publicKey != key.publicKey } val filtered = seals.filter { it.publicKey != key.verifyingKey }
return SealedBox(message, filtered + key.seal(message, expresAt), false) return SealedBox(message, filtered + key.seal(message, expresAt), false)
} }
/** /**
* Check that it is signed with a specified key. * Check that it is signed with a specified key.
*/ */
operator fun contains(publicKey: Signing.PublicKey): Boolean { operator fun contains(publicKey: SigningPublicKey): Boolean {
return seals.any { it.publicKey == publicKey } return seals.any { it.publicKey == publicKey }
} }
@ -77,7 +77,7 @@ class SealedBox(
* @param keys a list of keys to sign with, should be at least one key. * @param keys a list of keys to sign with, should be at least one key.
* @throws IllegalArgumentException if keys are not specified. * @throws IllegalArgumentException if keys are not specified.
*/ */
fun create(data: UByteArray, vararg keys: Signing.SecretKey): SealedBox { fun create(data: UByteArray, vararg keys: SigningSecretKey): SealedBox {
return SealedBox(data, keys.map { it.seal(data) }, false) return SealedBox(data, keys.map { it.seal(data) }, false)
} }
} }

View File

@ -1,107 +0,0 @@
package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.signature.InvalidSignatureException
import com.ionspin.kotlin.crypto.signature.Signature
import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.sergeych.crypto2.Signing.Companion.pair
import net.sergeych.utools.now
interface VerifyingKey {
val id: KeyId
/**
* Verify the signature and return true if it is correct.
*/
fun verify(signature: UByteArray, message: UByteArray): Boolean
}
interface SigningKey {
val publicKey: Signing.PublicKey
fun sign(message: UByteArray): UByteArray
fun seal(message: UByteArray, expiresAt: Instant? = null): Seal
}
/**
* Keys in general: public, secret and later symmetric too.
* Keys could be compared to each other for equality and used
* as a Map keys (not sure about js).
*
* Use [pair] to create new keys.
*/
@Serializable
sealed class Signing {
abstract val id: KeyId
abstract val packed: UByteArray
override fun equals(other: Any?): Boolean {
return other is Signing && other.packed contentEquals packed
}
override fun hashCode(): Int {
return packed.contentHashCode()
}
override fun toString(): String = packed.encodeToBase64Url()
/**
* Public key to verify signatures only
*/
@Serializable
@SerialName("p")
class PublicKey(override val packed: UByteArray) : Signing(), VerifyingKey {
/**
* Verify the signature and return true if it is correct.
*/
override fun verify(signature: UByteArray, message: UByteArray): Boolean = try {
Signature.verifyDetached(signature, message, packed)
true
} catch (_: InvalidSignatureException) {
false
}
override fun toString(): String = "Pub:${super.toString()}"
override val id: KeyId by lazy {
KeyId(KeysMagickNumbers.defaultSigning, packed)
}
}
/**
* Secret key to sign only
*/
@Serializable
@SerialName("s")
class SecretKey(override val packed: UByteArray) : Signing(), SigningKey {
override val publicKey: PublicKey by lazy {
PublicKey(Signature.ed25519SkToPk(packed))
}
override fun sign(message: UByteArray): UByteArray = Signature.detached(message, packed)
override fun seal(message: UByteArray, expiresAt: Instant?): Seal =
Seal.create(this, message, now(), expiresAt)
override fun toString(): String = "Sct:${super.toString()}"
override val id: KeyId
get() = publicKey.id
}
companion object {
data class Pair(val secretKey: SecretKey, val publicKey: PublicKey)
fun pair(): Pair {
val p = Signature.keypair()
return Pair(SecretKey(p.secretKey), PublicKey(p.publicKey))
}
}
}
open class IllegalSignatureException(text: String="signed data is tampered or signature is corrupted")
: IllegalStateException(text)
class ExpiredSignatureException(text: String): IllegalSignatureException(text)

View File

@ -0,0 +1,27 @@
package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.signature.InvalidSignatureException
import com.ionspin.kotlin.crypto.signature.Signature
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
/**
* Public key to verify signatures only
*/
@Serializable
class SigningPublicKey(override val keyBytes: UByteArray) : BinaryKeyBase(), VerifyingKey {
/**
* Verify the signature and return true if it is correct.
*/
override fun verify(signature: UByteArray, message: UByteArray): Boolean = try {
Signature.verifyDetached(signature, message, keyBytes)
true
} catch (_: InvalidSignatureException) {
false
}
override fun toString(): String = "Pub:${super.toString()}"
@Transient
override val magick: KeysMagickNumber = KeysMagickNumber.defaultVerifying
}

View File

@ -0,0 +1,47 @@
package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.signature.Signature
import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.sergeych.utools.now
/**
* Secret key to sign only
*/
@Serializable
class SigningSecretKey(
override val keyBytes: UByteArray,
@Transient
private var cachedPublicKey: SigningPublicKey?=null
) : BinaryKeyBase(), SigningKey {
override val verifyingKey: SigningPublicKey by lazy {
cachedPublicKey ?:
SigningPublicKey(Signature.ed25519SkToPk(keyBytes)).also { cachedPublicKey = it }
}
override fun sign(message: UByteArray): UByteArray = Signature.detached(message, keyBytes)
override fun seal(message: UByteArray, expiresAt: Instant?): Seal =
Seal.create(this, message, now(), expiresAt)
override fun toString(): String = "Sct:${super.toString()}"
@Transient
override val magick = KeysMagickNumber.defaultSigning
companion object {
data class SigningKeyPair(val secretKey: SigningSecretKey, val publicKey: SigningPublicKey)
fun generatePair(): SigningKeyPair {
val p = Signature.keypair()
val publicKey = SigningPublicKey(p.publicKey)
return SigningKeyPair(SigningSecretKey(p.secretKey, publicKey), publicKey)
}
fun new(): SigningSecretKey = generatePair().secretKey
}
}

View File

@ -37,7 +37,7 @@ class SymmetricKey(
override val nonceBytesLength: Int = nonceByteLength override val nonceBytesLength: Int = nonceByteLength
override val id by lazy { KeyId(KeysMagickNumbers.defaultSymmetric,blake2b3l(keyBytes)) } override val id by lazy { KeyId(KeysMagickNumber.defaultSymmetric,blake2b3l(keyBytes)) }
override fun decryptWithNonce(cipherData: UByteArray, nonce: UByteArray): UByteArray = override fun decryptWithNonce(cipherData: UByteArray, nonce: UByteArray): UByteArray =
protectDecryption { protectDecryption {
@ -60,7 +60,7 @@ class SymmetricKey(
/** /**
* Create a secure random symmetric key. * Create a secure random symmetric key.
*/ */
fun random() = SymmetricKey(SecretBox.keygen()) fun new() = SymmetricKey(SecretBox.keygen())
val nonceByteLength = crypto_secretbox_NONCEBYTES val nonceByteLength = crypto_secretbox_NONCEBYTES
} }

View File

@ -7,14 +7,6 @@ import kotlinx.serialization.Transient
@Serializable @Serializable
sealed class UniversalKey { sealed class UniversalKey {
abstract val id: KeyId abstract val id: KeyId
@Transient
open val canEncrypt = false
@Transient
open val canDecrypt = false
@Transient
open val canSign = false
@Transient
open val canVerify = false
@Serializable @Serializable
@SerialName("sym") @SerialName("sym")
@ -26,11 +18,6 @@ sealed class UniversalKey {
override val nonceBytesLength: Int = key.nonceBytesLength override val nonceBytesLength: Int = key.nonceBytesLength
override fun toString() = "U.Sym:$id" override fun toString() = "U.Sym:$id"
@Transient
override val canDecrypt: Boolean = true
@Transient
override val canEncrypt: Boolean = true
} }
@Serializable @Serializable
@ -41,37 +28,55 @@ sealed class UniversalKey {
override val id: KeyId = key.id override val id: KeyId = key.id
@Transient @Transient
override val nonceBytesLength: Int = key.nonceBytesLength override val nonceBytesLength: Int = key.nonceBytesLength
@Transient
override val canDecrypt: Boolean = true
@Transient
override val canEncrypt: Boolean = true
} }
@Serializable @Serializable
@SerialName("sec") @SerialName("sec")
data class Secret(val key: Asymmetric.SecretKey) : UniversalKey(), DecryptingKey by key { 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 val id: KeyId by lazy { key.id }
override fun toString() = "U.Sec:$id" override fun toString() = "U.Sec:$id"
@Transient }
override val canDecrypt: Boolean = true
@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.Sec:$id"
} }
@Serializable @Serializable
@SerialName("sig") @SerialName("sig")
data class Signing(val key: net.sergeych.crypto2.Signing.SecretKey) : UniversalKey() { data class Signing(val key: SigningSecretKey) : UniversalKey(), SigningKey by key {
override val id: KeyId by lazy { key.id } override val id: KeyId by lazy { key.id }
override fun toString() = "U.Sig:$id" override fun toString() = "U.Sig:$id"
@Transient
override val canDecrypt: Boolean = true /**
* [Verifying] key, e.g. [verifyingKey] wrapped in the [UniversalKey] variant.
*/
val publicKey by lazy { Verifying(verifyingKey) }
} }
@Serializable @Serializable
@SerialName("ver") @SerialName("ver")
data class Verifying(val key: net.sergeych.crypto2.Signing.PublicKey) : UniversalKey() { data class Verifying(val key: SigningPublicKey) : UniversalKey(), VerifyingKey by key {
override val id: KeyId by lazy { key.id } override val id: KeyId by lazy { key.id }
override fun toString() = "U.Sig:$id" override fun toString() = "U.Sig:$id"
@Transient
override val canDecrypt: Boolean = true
} }
@ -85,6 +90,14 @@ sealed class UniversalKey {
is SafeKeyExchange.SessionKey -> Session(key) is SafeKeyExchange.SessionKey -> Session(key)
else -> throw UnsupportedOperationException("can't create universal key from ${key::class.simpleName}") else -> throw UnsupportedOperationException("can't create universal key from ${key::class.simpleName}")
} }
fun newSecretKey(): Secret =
Secret(Asymmetric.new())
fun newSigningKey(): Signing =
Signing(SigningSecretKey.new())
@Suppress("unused")
fun newSymmetricKey(): Symmetric =
Symmetric(SymmetricKey.new())
} }
} }

View File

@ -4,27 +4,37 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
class UniversalRing( class UniversalRing(
private val keyWithTags: Map<UniversalKey,Set<String>> val keyWithTags: Map<UniversalKey,Set<String>>
) { ) {
val decryptingKeys: Set<DecryptingKey> by lazy {
keyWithTags.keys.mapNotNull { it as? DecryptingKey }.toSet()
}
constructor(vararg keys: UniversalKey) : this(keys.associateWith { setOf() }) constructor(vararg keys: UniversalKey) : this(keys.associateWith { setOf() })
constructor(vararg keys: DecryptingKey) : this(keys.associate { UniversalKey.from(it) to setOf<String>() }) constructor(vararg keys: DecryptingKey) : this(keys.associate { UniversalKey.from(it) to setOf<String>() })
constructor(vararg keyTags: Pair<UniversalKey, String>) constructor(vararg keyTags: Pair<UniversalKey, String>)
: this(keyTags.associate { it.first to setOf(it.second) }) : this(keyTags.associate { it.first to setOf(it.second) })
private val byId by lazy { keyWithTags.keys.associateBy { it.id } } val decryptingKeys: Set<DecryptingKey> by lazy { keys<DecryptingKey>() }
private val byIdWithTags by lazy { keyWithTags.entries.associate { it.key.id to (it.key to it.value) } }
operator fun get(keyId: KeyId): UniversalKey? = byId[keyId] inline fun <reified T>keys(): Set<T> =
keyWithTags.keys.mapNotNull { it as? T }.toSet()
inline fun <reified T>findKey(id: KeyId): UniversalKey? =
keyWithTags.keys.find { it is T && it.id == id }
fun allByAnyOfTags(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 }
@Suppress("unused")
inline fun <reified T>keyByAnyTag(vararg tags: String) = allByAnyOfTags(*tags).first { it is T }
operator fun get(keyId: KeyId): Collection<UniversalKey> = keyWithTags.keys.filter { it.id == keyId }
fun getTags(key: UniversalKey): Set<String>? = keyWithTags[key] fun getTags(key: UniversalKey): Set<String>? = keyWithTags[key]
fun keyWithTags(keyId: KeyId?): Pair<UniversalKey,Set<String>>? = byIdWithTags[keyId] operator fun contains(element: UniversalKey): Boolean = element in keyWithTags
operator fun contains(element: UniversalKey): Boolean = byId.containsKey(element.id)
operator fun plus(key: UniversalKey): UniversalRing = 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()) )
@ -56,9 +66,6 @@ class UniversalRing(
operator fun minus(key: UniversalKey): UniversalRing = 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
operator fun minus(keyId: KeyId): UniversalRing =
if( keyId in byId ) UniversalRing(keyWithTags.filter { it.key.id != keyId }) else this
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other !is UniversalRing) return false if (other !is UniversalRing) return false

View File

@ -9,8 +9,8 @@ class ContainerTest {
@Test @Test
fun testSingle() = runTest { fun testSingle() = runTest {
initCrypto() initCrypto()
val syk1 = SymmetricKey.random() val syk1 = SymmetricKey.new()
val syk2 = SymmetricKey.random() val syk2 = SymmetricKey.new()
val data = "sergeych, ohm many.".encodeToUByteArray() val data = "sergeych, ohm many.".encodeToUByteArray()
val c = Container.createWith(data, syk1) val c = Container.createWith(data, syk1)
@ -87,9 +87,9 @@ class ContainerTest {
@Test @Test
fun testMultipleSymmetric() = runTest { fun testMultipleSymmetric() = runTest {
initCrypto() initCrypto()
val syk1 = SymmetricKey.random() val syk1 = SymmetricKey.new()
val syk2 = SymmetricKey.random() val syk2 = SymmetricKey.new()
val syk3 = SymmetricKey.random() val syk3 = SymmetricKey.new()
val p1 = Asymmetric.generateKeys() val p1 = Asymmetric.generateKeys()
val p2 = Asymmetric.generateKeys() val p2 = Asymmetric.generateKeys()
val p3 = Asymmetric.generateKeys() val p3 = Asymmetric.generateKeys()
@ -131,9 +131,9 @@ class ContainerTest {
@Test @Test
fun testSingleGrowSymmetric() = runTest { fun testSingleGrowSymmetric() = runTest {
initCrypto() initCrypto()
val syk1 = SymmetricKey.random() val syk1 = SymmetricKey.new()
val syk2 = SymmetricKey.random() val syk2 = SymmetricKey.new()
val syk3 = SymmetricKey.random() val syk3 = SymmetricKey.new()
val p1 = Asymmetric.generateKeys() val p1 = Asymmetric.generateKeys()
val p3 = Asymmetric.generateKeys() val p3 = Asymmetric.generateKeys()
val p4 = Asymmetric.generateKeys() val p4 = Asymmetric.generateKeys()
@ -193,8 +193,8 @@ class ContainerTest {
@Test @Test
fun testSingleGrowAsymmetric() = runTest { fun testSingleGrowAsymmetric() = runTest {
initCrypto() initCrypto()
val syk1 = SymmetricKey.random() val syk1 = SymmetricKey.new()
val syk2 = SymmetricKey.random() val syk2 = SymmetricKey.new()
val p1 = Asymmetric.generateKeys() val p1 = Asymmetric.generateKeys()
val p3 = Asymmetric.generateKeys() val p3 = Asymmetric.generateKeys()
val data = "Translating the name 'Sergey Chernov' from Russian to archaic Sanskrit would be 'Ramo Krishna'" val data = "Translating the name 'Sergey Chernov' from Russian to archaic Sanskrit would be 'Ramo Krishna'"
@ -226,9 +226,9 @@ class ContainerTest {
@Test @Test
fun testMixedOps1() = runTest { fun testMixedOps1() = runTest {
initCrypto() initCrypto()
val syk1 = SymmetricKey.random() val syk1 = SymmetricKey.new()
val syk2 = SymmetricKey.random() val syk2 = SymmetricKey.new()
val syk3 = SymmetricKey.random() val syk3 = SymmetricKey.new()
val p1 = Asymmetric.generateKeys() val p1 = Asymmetric.generateKeys()
val p2 = Asymmetric.generateKeys() val p2 = Asymmetric.generateKeys()
val p3 = Asymmetric.generateKeys() val p3 = Asymmetric.generateKeys()
@ -263,9 +263,9 @@ class ContainerTest {
@Test @Test
fun testMixedOps2() = runTest { fun testMixedOps2() = runTest {
initCrypto() initCrypto()
val syk1 = SymmetricKey.random() val syk1 = SymmetricKey.new()
val syk2 = SymmetricKey.random() val syk2 = SymmetricKey.new()
val syk3 = SymmetricKey.random() val syk3 = SymmetricKey.new()
val p1 = Asymmetric.generateKeys() val p1 = Asymmetric.generateKeys()
val p2 = Asymmetric.generateKeys() val p2 = Asymmetric.generateKeys()
val p3 = Asymmetric.generateKeys() val p3 = Asymmetric.generateKeys()

View File

@ -13,11 +13,11 @@ class KeysTest {
@Test @Test
fun testSigningCreationAndMap() = runTest { fun testSigningCreationAndMap() = runTest {
initCrypto() initCrypto()
val (stk,pbk) = Signing.pair() val (stk,pbk) = SigningSecretKey.generatePair()
val x = mapOf( stk to "STK!", pbk to "PBK!") val x = mapOf( stk to "STK!", pbk to "PBK!")
assertEquals("STK!", x[stk]) assertEquals("STK!", x[stk])
val s1 = Signing.SecretKey(stk.packed) val s1 = SigningSecretKey(stk.keyBytes)
assertEquals(stk, s1) assertEquals(stk, s1)
assertEquals("STK!", x[s1]) assertEquals("STK!", x[s1])
assertEquals("PBK!", x[pbk]) assertEquals("PBK!", x[pbk])
@ -29,8 +29,8 @@ class KeysTest {
data1[0] = 0x01u data1[0] = 0x01u
assertFalse(s.isValid(data1)) assertFalse(s.isValid(data1))
val p2 = Signing.pair() val p2 = SigningSecretKey.generatePair()
val p3 = Signing.pair() val p3 = SigningSecretKey.generatePair()
val ms = SealedBox.create(data, s1) + p2.secretKey val ms = SealedBox.create(data, s1) + p2.secretKey
@ -49,8 +49,8 @@ class KeysTest {
@Test @Test
fun testNonDeterministicSeals() = runTest { fun testNonDeterministicSeals() = runTest {
initCrypto() initCrypto()
val data = "Welcome to the Miami, bitch!".encodeToUByteArray() val data = "Welcome to the crazy new world!".encodeToUByteArray()
val (sk,_) = Signing.pair() val (sk,_) = SigningSecretKey.generatePair()
val t = now() val t = now()
val s1 = Seal.create(sk, data, createdAt = t) val s1 = Seal.create(sk, data, createdAt = t)
val s2 = Seal.create(sk, data, createdAt = t) val s2 = Seal.create(sk, data, createdAt = t)
@ -72,8 +72,8 @@ class KeysTest {
@Test @Test
fun secretEncryptTest() = runTest { fun secretEncryptTest() = runTest {
initCrypto() initCrypto()
val key = SymmetricKey.random() val key = SymmetricKey.new()
val key1 = SymmetricKey.random() val key1 = SymmetricKey.new()
assertEquals("hello", key.decrypt(key.encrypt("hello".encodeToUByteArray())).decodeFromUByteArray()) assertEquals("hello", key.decrypt(key.encrypt("hello".encodeToUByteArray())).decodeFromUByteArray())
assertEquals("hello", key.decryptString(key.encrypt("hello"))) assertEquals("hello", key.decryptString(key.encrypt("hello")))
assertEquals("hello", key.decryptObject(key.encryptObject("hello"))) assertEquals("hello", key.decryptObject(key.encryptObject("hello")))
@ -88,7 +88,7 @@ class KeysTest {
@Test @Test
fun symmetricKeyTest() = runTest { fun symmetricKeyTest() = runTest {
initCrypto() initCrypto()
val k1 = SymmetricKey.random() val k1 = SymmetricKey.new()
val src = "Buena Vista".encodeToUByteArray() val src = "Buena Vista".encodeToUByteArray()
val nonce = k1.randomNonce() val nonce = k1.randomNonce()
@ -176,9 +176,9 @@ class KeysTest {
@Test @Test
fun testUniKeys() = runTest { fun testUniKeys() = runTest {
initCrypto() initCrypto()
val sy1 = SymmetricKey.random() val sy1 = SymmetricKey.new()
val sy2 = SymmetricKey(sy1.keyBytes) val sy2 = SymmetricKey(sy1.keyBytes)
val sy3 = SymmetricKey.random() val sy3 = SymmetricKey.new()
assertEquals(sy1, sy2) assertEquals(sy1, sy2)
assertEquals(sy2, sy1) assertEquals(sy2, sy1)
@ -195,9 +195,9 @@ class KeysTest {
assertEquals(usy2, usy1) assertEquals(usy2, usy1)
assertFalse { usy1 == usy3 } assertFalse { usy1 == usy3 }
val sk1 = Asymmetric.randomSecretKey() val sk1 = Asymmetric.new()
val sk2 = Asymmetric.SecretKey(sk1.keyBytes) val sk2 = Asymmetric.SecretKey(sk1.keyBytes)
val sk3 = Asymmetric.randomSecretKey() val sk3 = Asymmetric.new()
assertEquals(sk1, sk2) assertEquals(sk1, sk2)
assertEquals(sk2, sk1) assertEquals(sk2, sk1)

View File

@ -16,7 +16,7 @@ class RingTest {
val y2 = SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray()) val y2 = SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray())
assertEquals(y1, y2) assertEquals(y1, y2)
val e1 = Asymmetric.randomSecretKey() val e1 = Asymmetric.new()
val e2: Asymmetric.SecretKey = BipackDecoder.decode(BipackEncoder.encode(e1)) val e2: Asymmetric.SecretKey = BipackDecoder.decode(BipackEncoder.encode(e1))
assertEquals(e1, e2) assertEquals(e1, e2)
@ -26,8 +26,8 @@ class RingTest {
assertEquals(k1, k11) assertEquals(k1, k11)
val k2 = UniversalKey.from(Asymmetric.randomSecretKey()) val k2 = UniversalKey.from(Asymmetric.new())
val k3 = UniversalKey.from(Asymmetric.randomSecretKey()) val k3 = UniversalKey.from(Asymmetric.new())
// //
val r = UniversalRing(k1, k2) val r = UniversalRing(k1, k2)
// val r = UniversalRing(k1) // val r = UniversalRing(k1)
@ -35,8 +35,8 @@ class RingTest {
assertTrue(k1 in r) assertTrue(k1 in r)
assertFalse { k3 in r } assertFalse { k3 in r }
println(Asymmetric.randomSecretKey().keyBytes.size) println(Asymmetric.new().keyBytes.size)
println(BipackEncoder.encode(Asymmetric.randomSecretKey()).size) println(BipackEncoder.encode(Asymmetric.new()).size)
val encoded = BipackEncoder.encode(r) val encoded = BipackEncoder.encode(r)
println(encoded.toDump()) println(encoded.toDump())
println(encoded.size) println(encoded.size)
@ -60,16 +60,8 @@ class RingTest {
fun testTags() = runTest { fun testTags() = runTest {
initCrypto() initCrypto()
val y1 = SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray())
val y2 = SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray())
val e1 = Asymmetric.randomSecretKey()
val e2: Asymmetric.SecretKey = BipackDecoder.decode(BipackEncoder.encode(e1))
val k1 = UniversalKey.from(SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray())) val k1 = UniversalKey.from(SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray()))
val k11 = UniversalKey.from(SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray())) val k2 = UniversalKey.from(Asymmetric.new())
val k2 = UniversalKey.from(Asymmetric.randomSecretKey())
val k3 = UniversalKey.from(Asymmetric.randomSecretKey())
val r1 = UniversalRing(k1, k2) val r1 = UniversalRing(k1, k2)
var r2 = UniversalRing(deepCopy(k1), deepCopy(k2)) var r2 = UniversalRing(deepCopy(k1), deepCopy(k2))
@ -91,6 +83,35 @@ class RingTest {
assertEquals(r2, r3) assertEquals(r2, r3)
} }
@Test
fun testAsymmetricEncryption() = runTest {
initCrypto()
val sk1 = UniversalKey.newSecretKey()
val sk2 = UniversalKey.newSecretKey()
val sk3 = UniversalKey.newSecretKey()
// val sk4 = UniversalKey.newSecretKey()
val sik1 = UniversalKey.newSigningKey()
// val sik2 = UniversalKey.newSigningKey()
val sik3 = UniversalKey.newSigningKey()
val data = "Mendeleev' table".encodeToUByteArray()
val r = UniversalRing(sk2, sk3, sk1.publicKey, sk3.publicKey, sik3.publicKey, sik1)
r.addTags(sik1, "SECRET_SIGN")
val box = deepCopy(Container.create(data) { key(sk3.publicKey) })
assertContentEquals(data, box.decryptWith(r))
assertEquals(sk3.publicKey, r.findKey<EncryptingKey>(sk3.id))
assertTrue { sik3.publicKey in r }
assertEquals(sik1, r.findKey<EncryptingKey>(sik1.publicKey.id))
assertEquals(sik1, r.keyByTag<SigningKey>("SECRET_SIGN"))
}
@Test @Test
fun testSize() = runTest { fun testSize() = runTest {
// val sy1 = SymmetricKey.random().toUniversal() // val sy1 = SymmetricKey.random().toUniversal()