From ede55650f42fd487bf1fdc0ed79db7b79a284d00 Mon Sep 17 00:00:00 2001 From: sergeych Date: Sat, 22 Jun 2024 18:18:14 +0700 Subject: [PATCH] refactored keys, unversal keys and ring --- .../kotlin/net/sergeych/crypto2/Asymmetric.kt | 39 ++----- .../net/sergeych/crypto2/BinaryKeyBase.kt | 47 ++++++++ .../kotlin/net/sergeych/crypto2/Container.kt | 10 +- .../kotlin/net/sergeych/crypto2/KeyId.kt | 2 +- .../net/sergeych/crypto2/MagickNumbers.kt | 5 +- .../kotlin/net/sergeych/crypto2/Seal.kt | 8 +- .../kotlin/net/sergeych/crypto2/SealedBox.kt | 14 +-- .../kotlin/net/sergeych/crypto2/Signing.kt | 107 ------------------ .../net/sergeych/crypto2/SigningPublicKey.kt | 27 +++++ .../net/sergeych/crypto2/SigningSecretKey.kt | 47 ++++++++ .../net/sergeych/crypto2/SymmetricKey.kt | 4 +- .../net/sergeych/crypto2/UniversalKey.kt | 63 +++++++---- .../net/sergeych/crypto2/UniversalRing.kt | 35 +++--- src/commonTest/kotlin/ContainerTest.kt | 32 +++--- src/commonTest/kotlin/KeysTest.kt | 26 ++--- src/commonTest/kotlin/RingTest.kt | 49 +++++--- 16 files changed, 274 insertions(+), 241 deletions(-) create mode 100644 src/commonMain/kotlin/net/sergeych/crypto2/BinaryKeyBase.kt delete mode 100644 src/commonMain/kotlin/net/sergeych/crypto2/Signing.kt create mode 100644 src/commonMain/kotlin/net/sergeych/crypto2/SigningPublicKey.kt create mode 100644 src/commonMain/kotlin/net/sergeych/crypto2/SigningSecretKey.kt diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/Asymmetric.kt b/src/commonMain/kotlin/net/sergeych/crypto2/Asymmetric.kt index 4c2f4c0..05a4e68 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/Asymmetric.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/Asymmetric.kt @@ -109,7 +109,7 @@ object Asymmetric { private fun randomNonce(): UByteArray = randomUBytes(crypto_box_NONCEBYTES) - fun randomSecretKey(): SecretKey = generateKeys().secretKey + fun new(): SecretKey = generateKeys().secretKey @Suppress("unused") val nonceBytesLength = crypto_box_NONCEBYTES @@ -121,22 +121,9 @@ object Asymmetric { * Anonymous encryption is very slow in comparison. */ @Serializable - class PublicKey(val keyBytes: UByteArray) { + class PublicKey(override val keyBytes: UByteArray) : BinaryKeyBase() { - val tag: KeyId by lazy { - 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() - } + override val magick: KeysMagickNumber = KeysMagickNumber.defaultAssymmetric /** * Create an anonymous message that could be decrypted only with the [SecretKey] that corresponds this. @@ -171,7 +158,7 @@ object Asymmetric { fun encryptMessage( plainData: UByteArray, nonce: UByteArray = randomNonce(), - senderKey: SecretKey = randomSecretKey(), + senderKey: SecretKey = new(), randomFill: IntRange? = null, ) = createMessage(senderKey, this, WithFill.encode(plainData, randomFill), nonce) @@ -192,10 +179,10 @@ object Asymmetric { */ @Serializable class SecretKey( - val keyBytes: UByteArray, + override val keyBytes: UByteArray, @Transient val _cachedPublicKey: PublicKey? = null, - ) : DecryptingKey { + ) : DecryptingKey, BinaryKeyBase() { /** * Decrypt with authentication checks the message which must have [Message.senderPublicKey] set. @@ -226,17 +213,6 @@ object Asymmetric { .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 */ @@ -250,7 +226,8 @@ object Asymmetric { 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 get() = 0 diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/BinaryKeyBase.kt b/src/commonMain/kotlin/net/sergeych/crypto2/BinaryKeyBase.kt new file mode 100644 index 0000000..0547fdf --- /dev/null +++ b/src/commonMain/kotlin/net/sergeych/crypto2/BinaryKeyBase.kt @@ -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) \ No newline at end of file diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt b/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt index 73fea70..1748e8c 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/Container.kt @@ -232,10 +232,10 @@ sealed class Container { constructor(sender: Asymmetric.SecretKey?, recipient: Asymmetric.PublicKey, encodeMainKey: UByteArray) : this( - recipient.tag, + recipient.id, recipient.encryptMessage( encodeMainKey, - senderKey = sender ?: Asymmetric.randomSecretKey(), + senderKey = sender ?: Asymmetric.new(), ).encoded ) } @@ -375,7 +375,7 @@ sealed class Container { mainKey = p.mainKey ?: throw IllegalStateException("parent container must be decrypted") } else { eks = mutableListOf() - mainKey = SymmetricKey.random() + mainKey = SymmetricKey.new() } val encodedMainKey = BipackEncoder.encode(mainKey).toUByteArray() createMulti(eks, encodedMainKey, mainKey) @@ -389,9 +389,9 @@ sealed class Container { val pair = keyPairs.first() val (sk, pk) = pair Single( - pk.tag, pk.encryptMessage( + pk.id, pk.encryptMessage( plainData, - senderKey = sk ?: Asymmetric.randomSecretKey(), + senderKey = sk ?: Asymmetric.new(), randomFill = fillRange ).encoded, plainData, diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/KeyId.kt b/src/commonMain/kotlin/net/sergeych/crypto2/KeyId.kt index 2316315..be318eb 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/KeyId.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/KeyId.kt @@ -23,6 +23,6 @@ data class KeyId(val id: BinaryId, val kdp: KeyDerivationParams?=null ) { 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) } \ No newline at end of file diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/MagickNumbers.kt b/src/commonMain/kotlin/net/sergeych/crypto2/MagickNumbers.kt index ae081bb..f89953b 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/MagickNumbers.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/MagickNumbers.kt @@ -1,10 +1,11 @@ package net.sergeych.crypto2 -enum class KeysMagickNumbers(val number: Int) { +enum class KeysMagickNumber(val number: Int) { defaultAssymmetric(0), defaultSymmetric(1), defaultSession(2), defaultSigning(3), + defaultVerifying(4), ; companion object { @@ -12,6 +13,6 @@ enum class KeysMagickNumbers(val number: Int) { val forNumber = entries.map { it.number to it }.toMap() @Suppress("unused") - fun findFor(binaryId: BinaryId): KeysMagickNumbers? = forNumber[binaryId.magick] + fun findFor(binaryId: BinaryId): KeysMagickNumber? = forNumber[binaryId.magick] } } \ No newline at end of file diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/Seal.kt b/src/commonMain/kotlin/net/sergeych/crypto2/Seal.kt index 7a913d0..c2aec5d 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/Seal.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/Seal.kt @@ -23,7 +23,7 @@ import net.sergeych.utools.now */ @Serializable class Seal( - val publicKey: Signing.PublicKey, + val publicKey: SigningPublicKey, val signature: UByteArray, val nonce: UByteArray?, val createdAt: Instant, @@ -95,7 +95,7 @@ class Seal( * Seal [message] with a [key]. * * 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. * * 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. */ fun create( - key: Signing.SecretKey, + key: SigningSecretKey, message: UByteArray, createdAt: Instant = now(), expiresAt: Instant? = null, @@ -138,7 +138,7 @@ class Seal( ): Seal { val nonce = if( nonDeterministic ) randomUBytes(32) else null 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) } /** diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/SealedBox.kt b/src/commonMain/kotlin/net/sergeych/crypto2/SealedBox.kt index b295cb8..cb61218 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/SealedBox.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/SealedBox.kt @@ -30,7 +30,7 @@ class SealedBox( ) { @Suppress("unused") - constructor(message: UByteArray, vararg keys: Signing.SecretKey) : + constructor(message: UByteArray, vararg keys: SigningSecretKey) : 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 * _can't assume it always returns a copied object!_ */ - operator fun plus(key: Signing.SecretKey): SealedBox = - if (key.publicKey in this) this + operator fun plus(key: SigningSecretKey): SealedBox = + if (key.verifyingKey in this) this else SealedBox(message, seals + key.seal(message),false) /** * Add expiring seal, otherwise use [plus]. Overrides exising seal for [key] * if present: */ - fun addSeal(key: Signing.SecretKey, expresAt: Instant): SealedBox { - val filtered = seals.filter { it.publicKey != key.publicKey } + fun addSeal(key: SigningSecretKey, expresAt: Instant): SealedBox { + val filtered = seals.filter { it.publicKey != key.verifyingKey } return SealedBox(message, filtered + key.seal(message, expresAt), false) } /** * 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 } } @@ -77,7 +77,7 @@ class SealedBox( * @param keys a list of keys to sign with, should be at least one key. * @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) } } diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/Signing.kt b/src/commonMain/kotlin/net/sergeych/crypto2/Signing.kt deleted file mode 100644 index 6278066..0000000 --- a/src/commonMain/kotlin/net/sergeych/crypto2/Signing.kt +++ /dev/null @@ -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) \ No newline at end of file diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/SigningPublicKey.kt b/src/commonMain/kotlin/net/sergeych/crypto2/SigningPublicKey.kt new file mode 100644 index 0000000..679dcd0 --- /dev/null +++ b/src/commonMain/kotlin/net/sergeych/crypto2/SigningPublicKey.kt @@ -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 +} \ No newline at end of file diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/SigningSecretKey.kt b/src/commonMain/kotlin/net/sergeych/crypto2/SigningSecretKey.kt new file mode 100644 index 0000000..b74b72d --- /dev/null +++ b/src/commonMain/kotlin/net/sergeych/crypto2/SigningSecretKey.kt @@ -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 + + } +} \ No newline at end of file diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt b/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt index 78d474a..3c80e1f 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt @@ -37,7 +37,7 @@ class SymmetricKey( 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 = protectDecryption { @@ -60,7 +60,7 @@ class SymmetricKey( /** * Create a secure random symmetric key. */ - fun random() = SymmetricKey(SecretBox.keygen()) + fun new() = SymmetricKey(SecretBox.keygen()) val nonceByteLength = crypto_secretbox_NONCEBYTES } diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/UniversalKey.kt b/src/commonMain/kotlin/net/sergeych/crypto2/UniversalKey.kt index 4313f6d..9cca181 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/UniversalKey.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/UniversalKey.kt @@ -7,14 +7,6 @@ import kotlinx.serialization.Transient @Serializable sealed class UniversalKey { 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 @SerialName("sym") @@ -26,11 +18,6 @@ sealed class UniversalKey { override val nonceBytesLength: Int = key.nonceBytesLength override fun toString() = "U.Sym:$id" - - @Transient - override val canDecrypt: Boolean = true - @Transient - override val canEncrypt: Boolean = true } @Serializable @@ -41,37 +28,55 @@ sealed class UniversalKey { override val id: KeyId = key.id @Transient override val nonceBytesLength: Int = key.nonceBytesLength - @Transient - override val canDecrypt: Boolean = true - @Transient - override val canEncrypt: Boolean = true } @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" - @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 @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 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 @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 fun toString() = "U.Sig:$id" - @Transient - override val canDecrypt: Boolean = true } @@ -85,6 +90,14 @@ sealed class UniversalKey { is SafeKeyExchange.SessionKey -> Session(key) 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()) } } diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/UniversalRing.kt b/src/commonMain/kotlin/net/sergeych/crypto2/UniversalRing.kt index 7df4d9e..0ea3d2e 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/UniversalRing.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/UniversalRing.kt @@ -4,27 +4,37 @@ import kotlinx.serialization.Serializable @Serializable class UniversalRing( - private val keyWithTags: Map> + val keyWithTags: Map> ) { - val decryptingKeys: Set by lazy { - keyWithTags.keys.mapNotNull { it as? DecryptingKey }.toSet() - } - constructor(vararg keys: UniversalKey) : this(keys.associateWith { setOf() }) constructor(vararg keys: DecryptingKey) : this(keys.associate { UniversalKey.from(it) to setOf() }) constructor(vararg keyTags: Pair) : this(keyTags.associate { it.first to setOf(it.second) }) - private val byId by lazy { keyWithTags.keys.associateBy { it.id } } - private val byIdWithTags by lazy { keyWithTags.entries.associate { it.key.id to (it.key to it.value) } } + val decryptingKeys: Set by lazy { keys() } - operator fun get(keyId: KeyId): UniversalKey? = byId[keyId] + inline fun keys(): Set = + keyWithTags.keys.mapNotNull { it as? T }.toSet() + + inline fun 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 keyByTag(tag: String) = allByAnyOfTags(tag).first { it is T } + + @Suppress("unused") + inline fun keyByAnyTag(vararg tags: String) = allByAnyOfTags(*tags).first { it is T } + + operator fun get(keyId: KeyId): Collection = keyWithTags.keys.filter { it.id == keyId } fun getTags(key: UniversalKey): Set? = keyWithTags[key] - fun keyWithTags(keyId: KeyId?): Pair>? = byIdWithTags[keyId] - - operator fun contains(element: UniversalKey): Boolean = byId.containsKey(element.id) + operator fun contains(element: UniversalKey): Boolean = element in keyWithTags operator fun plus(key: UniversalKey): UniversalRing = if( key in this ) this else UniversalRing(keyWithTags + (key to setOf()) ) @@ -56,9 +66,6 @@ class UniversalRing( operator fun minus(key: UniversalKey): UniversalRing = 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 { if (this === other) return true if (other !is UniversalRing) return false diff --git a/src/commonTest/kotlin/ContainerTest.kt b/src/commonTest/kotlin/ContainerTest.kt index 70dd22e..162e33a 100644 --- a/src/commonTest/kotlin/ContainerTest.kt +++ b/src/commonTest/kotlin/ContainerTest.kt @@ -9,8 +9,8 @@ class ContainerTest { @Test fun testSingle() = runTest { initCrypto() - val syk1 = SymmetricKey.random() - val syk2 = SymmetricKey.random() + val syk1 = SymmetricKey.new() + val syk2 = SymmetricKey.new() val data = "sergeych, ohm many.".encodeToUByteArray() val c = Container.createWith(data, syk1) @@ -87,9 +87,9 @@ class ContainerTest { @Test fun testMultipleSymmetric() = runTest { initCrypto() - val syk1 = SymmetricKey.random() - val syk2 = SymmetricKey.random() - val syk3 = SymmetricKey.random() + val syk1 = SymmetricKey.new() + val syk2 = SymmetricKey.new() + val syk3 = SymmetricKey.new() val p1 = Asymmetric.generateKeys() val p2 = Asymmetric.generateKeys() val p3 = Asymmetric.generateKeys() @@ -131,9 +131,9 @@ class ContainerTest { @Test fun testSingleGrowSymmetric() = runTest { initCrypto() - val syk1 = SymmetricKey.random() - val syk2 = SymmetricKey.random() - val syk3 = SymmetricKey.random() + val syk1 = SymmetricKey.new() + val syk2 = SymmetricKey.new() + val syk3 = SymmetricKey.new() val p1 = Asymmetric.generateKeys() val p3 = Asymmetric.generateKeys() val p4 = Asymmetric.generateKeys() @@ -193,8 +193,8 @@ class ContainerTest { @Test fun testSingleGrowAsymmetric() = runTest { initCrypto() - val syk1 = SymmetricKey.random() - val syk2 = SymmetricKey.random() + val syk1 = SymmetricKey.new() + val syk2 = SymmetricKey.new() val p1 = Asymmetric.generateKeys() val p3 = Asymmetric.generateKeys() val data = "Translating the name 'Sergey Chernov' from Russian to archaic Sanskrit would be 'Ramo Krishna'" @@ -226,9 +226,9 @@ class ContainerTest { @Test fun testMixedOps1() = runTest { initCrypto() - val syk1 = SymmetricKey.random() - val syk2 = SymmetricKey.random() - val syk3 = SymmetricKey.random() + val syk1 = SymmetricKey.new() + val syk2 = SymmetricKey.new() + val syk3 = SymmetricKey.new() val p1 = Asymmetric.generateKeys() val p2 = Asymmetric.generateKeys() val p3 = Asymmetric.generateKeys() @@ -263,9 +263,9 @@ class ContainerTest { @Test fun testMixedOps2() = runTest { initCrypto() - val syk1 = SymmetricKey.random() - val syk2 = SymmetricKey.random() - val syk3 = SymmetricKey.random() + val syk1 = SymmetricKey.new() + val syk2 = SymmetricKey.new() + val syk3 = SymmetricKey.new() val p1 = Asymmetric.generateKeys() val p2 = Asymmetric.generateKeys() val p3 = Asymmetric.generateKeys() diff --git a/src/commonTest/kotlin/KeysTest.kt b/src/commonTest/kotlin/KeysTest.kt index 98b6667..c1a82c6 100644 --- a/src/commonTest/kotlin/KeysTest.kt +++ b/src/commonTest/kotlin/KeysTest.kt @@ -13,11 +13,11 @@ class KeysTest { @Test fun testSigningCreationAndMap() = runTest { initCrypto() - val (stk,pbk) = Signing.pair() + val (stk,pbk) = SigningSecretKey.generatePair() val x = mapOf( stk to "STK!", pbk to "PBK!") assertEquals("STK!", x[stk]) - val s1 = Signing.SecretKey(stk.packed) + val s1 = SigningSecretKey(stk.keyBytes) assertEquals(stk, s1) assertEquals("STK!", x[s1]) assertEquals("PBK!", x[pbk]) @@ -29,8 +29,8 @@ class KeysTest { data1[0] = 0x01u assertFalse(s.isValid(data1)) - val p2 = Signing.pair() - val p3 = Signing.pair() + val p2 = SigningSecretKey.generatePair() + val p3 = SigningSecretKey.generatePair() val ms = SealedBox.create(data, s1) + p2.secretKey @@ -49,8 +49,8 @@ class KeysTest { @Test fun testNonDeterministicSeals() = runTest { initCrypto() - val data = "Welcome to the Miami, bitch!".encodeToUByteArray() - val (sk,_) = Signing.pair() + val data = "Welcome to the crazy new world!".encodeToUByteArray() + val (sk,_) = SigningSecretKey.generatePair() val t = now() val s1 = Seal.create(sk, data, createdAt = t) val s2 = Seal.create(sk, data, createdAt = t) @@ -72,8 +72,8 @@ class KeysTest { @Test fun secretEncryptTest() = runTest { initCrypto() - val key = SymmetricKey.random() - val key1 = SymmetricKey.random() + val key = SymmetricKey.new() + val key1 = SymmetricKey.new() assertEquals("hello", key.decrypt(key.encrypt("hello".encodeToUByteArray())).decodeFromUByteArray()) assertEquals("hello", key.decryptString(key.encrypt("hello"))) assertEquals("hello", key.decryptObject(key.encryptObject("hello"))) @@ -88,7 +88,7 @@ class KeysTest { @Test fun symmetricKeyTest() = runTest { initCrypto() - val k1 = SymmetricKey.random() + val k1 = SymmetricKey.new() val src = "Buena Vista".encodeToUByteArray() val nonce = k1.randomNonce() @@ -176,9 +176,9 @@ class KeysTest { @Test fun testUniKeys() = runTest { initCrypto() - val sy1 = SymmetricKey.random() + val sy1 = SymmetricKey.new() val sy2 = SymmetricKey(sy1.keyBytes) - val sy3 = SymmetricKey.random() + val sy3 = SymmetricKey.new() assertEquals(sy1, sy2) assertEquals(sy2, sy1) @@ -195,9 +195,9 @@ class KeysTest { assertEquals(usy2, usy1) assertFalse { usy1 == usy3 } - val sk1 = Asymmetric.randomSecretKey() + val sk1 = Asymmetric.new() val sk2 = Asymmetric.SecretKey(sk1.keyBytes) - val sk3 = Asymmetric.randomSecretKey() + val sk3 = Asymmetric.new() assertEquals(sk1, sk2) assertEquals(sk2, sk1) diff --git a/src/commonTest/kotlin/RingTest.kt b/src/commonTest/kotlin/RingTest.kt index 09097d2..f5b19d4 100644 --- a/src/commonTest/kotlin/RingTest.kt +++ b/src/commonTest/kotlin/RingTest.kt @@ -16,7 +16,7 @@ class RingTest { val y2 = SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray()) assertEquals(y1, y2) - val e1 = Asymmetric.randomSecretKey() + val e1 = Asymmetric.new() val e2: Asymmetric.SecretKey = BipackDecoder.decode(BipackEncoder.encode(e1)) assertEquals(e1, e2) @@ -26,8 +26,8 @@ class RingTest { assertEquals(k1, k11) - val k2 = UniversalKey.from(Asymmetric.randomSecretKey()) - val k3 = UniversalKey.from(Asymmetric.randomSecretKey()) + val k2 = UniversalKey.from(Asymmetric.new()) + val k3 = UniversalKey.from(Asymmetric.new()) // val r = UniversalRing(k1, k2) // val r = UniversalRing(k1) @@ -35,8 +35,8 @@ class RingTest { assertTrue(k1 in r) assertFalse { k3 in r } - println(Asymmetric.randomSecretKey().keyBytes.size) - println(BipackEncoder.encode(Asymmetric.randomSecretKey()).size) + println(Asymmetric.new().keyBytes.size) + println(BipackEncoder.encode(Asymmetric.new()).size) val encoded = BipackEncoder.encode(r) println(encoded.toDump()) println(encoded.size) @@ -60,16 +60,8 @@ class RingTest { fun testTags() = runTest { 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 k11 = UniversalKey.from(SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray())) - val k2 = UniversalKey.from(Asymmetric.randomSecretKey()) - val k3 = UniversalKey.from(Asymmetric.randomSecretKey()) + val k2 = UniversalKey.from(Asymmetric.new()) val r1 = UniversalRing(k1, k2) var r2 = UniversalRing(deepCopy(k1), deepCopy(k2)) @@ -91,6 +83,35 @@ class RingTest { 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(sk3.id)) + assertTrue { sik3.publicKey in r } + assertEquals(sik1, r.findKey(sik1.publicKey.id)) + + assertEquals(sik1, r.keyByTag("SECRET_SIGN")) + + } + @Test fun testSize() = runTest { // val sy1 = SymmetricKey.random().toUniversal()