Asymmetric.Public now can be converted to Universal and stored into a ring
This commit is contained in:
parent
bb383b5457
commit
435604379e
@ -109,7 +109,7 @@ object Asymmetric {
|
||||
|
||||
private fun randomNonce(): UByteArray = randomUBytes(crypto_box_NONCEBYTES)
|
||||
|
||||
fun new(): SecretKey = generateKeys().secretKey
|
||||
fun newSecretKey(): SecretKey = generateKeys().secretKey
|
||||
|
||||
@Suppress("unused")
|
||||
val nonceBytesLength = crypto_box_NONCEBYTES
|
||||
@ -121,7 +121,7 @@ object Asymmetric {
|
||||
* Anonymous encryption is very slow in comparison.
|
||||
*/
|
||||
@Serializable
|
||||
class PublicKey(override val keyBytes: UByteArray) : BinaryKeyBase() {
|
||||
class PublicKey(override val keyBytes: UByteArray) : BinaryKeyBase(), KeyInstance {
|
||||
|
||||
override val magick: KeysMagickNumber = KeysMagickNumber.defaultAssymmetric
|
||||
|
||||
@ -137,7 +137,7 @@ object Asymmetric {
|
||||
* proves that the message was not altered after creation.
|
||||
*/
|
||||
fun encryptAnonymousMessage(plainData: UByteArray, randomFill: IntRange? = null): Message =
|
||||
encryptMessage(plainData,randomFill=randomFill)
|
||||
encryptMessage(plainData, randomFill = randomFill)
|
||||
|
||||
/**
|
||||
* Anonymous encryption, see [encryptAnonymousMessage], to binary data. Sender could not be identified.
|
||||
@ -158,7 +158,7 @@ object Asymmetric {
|
||||
fun encryptMessage(
|
||||
plainData: UByteArray,
|
||||
nonce: UByteArray = randomNonce(),
|
||||
senderKey: SecretKey = new(),
|
||||
senderKey: SecretKey = newSecretKey(),
|
||||
randomFill: IntRange? = null,
|
||||
) = createMessage(senderKey, this, WithFill.encode(plainData, randomFill), nonce)
|
||||
|
||||
@ -167,11 +167,14 @@ object Asymmetric {
|
||||
* [SecretKey] corresponding to this one, will be able to decrypt the message and be sure that [senderKey]
|
||||
* was the author and the message was not altered.
|
||||
*/
|
||||
fun encryptMessage(plainData: UByteArray,
|
||||
senderKey: SecretKey,
|
||||
randomFill: IntRange? = null): Message =
|
||||
fun encryptMessage(
|
||||
plainData: UByteArray,
|
||||
senderKey: SecretKey,
|
||||
randomFill: IntRange? = null,
|
||||
): Message =
|
||||
createMessage(senderKey, this, WithFill.encode(plainData, randomFill))
|
||||
|
||||
fun toUniversalKey(): UniversalKey.Public = UniversalKey.Public(this)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -209,8 +212,11 @@ object Asymmetric {
|
||||
* The corresponding public key
|
||||
*/
|
||||
val publicKey: PublicKey by lazy {
|
||||
PublicKey(ScalarMultiplication.scalarMultiplicationBase(keyBytes))
|
||||
.also { cachedPublicKey = it }
|
||||
if (cachedPublicKey != null)
|
||||
cachedPublicKey!!
|
||||
else
|
||||
PublicKey(ScalarMultiplication.scalarMultiplicationBase(keyBytes))
|
||||
.also { cachedPublicKey = it }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -240,4 +246,4 @@ object Asymmetric {
|
||||
* Shortcut type: a pair of sender secret key and recipient private key could be used so
|
||||
* simplify such interfaces
|
||||
*/
|
||||
typealias AsymmetricEncryptionPair = Pair<SecretKey?,PublicKey>
|
||||
typealias AsymmetricEncryptionPair = Pair<SecretKey?, PublicKey>
|
||||
|
@ -1,23 +1,7 @@
|
||||
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() {
|
||||
|
||||
|
@ -235,7 +235,7 @@ sealed class Container {
|
||||
recipient.id,
|
||||
recipient.encryptMessage(
|
||||
encodeMainKey,
|
||||
senderKey = sender ?: Asymmetric.new(),
|
||||
senderKey = sender ?: Asymmetric.newSecretKey(),
|
||||
).encoded
|
||||
)
|
||||
}
|
||||
@ -391,7 +391,7 @@ sealed class Container {
|
||||
Single(
|
||||
pk.id, pk.encryptMessage(
|
||||
plainData,
|
||||
senderKey = sk ?: Asymmetric.new(),
|
||||
senderKey = sk ?: Asymmetric.newSecretKey(),
|
||||
randomFill = fillRange
|
||||
).encoded,
|
||||
plainData,
|
||||
|
@ -9,7 +9,7 @@ import net.sergeych.crypto2.SymmetricKey.WithNonce
|
||||
* Some key able to perform decrypting. It is not serializable by purpose, as not all such
|
||||
* keys are wise to transfer/save. Concrete implementations are, like [SymmetricKey].
|
||||
*/
|
||||
interface DecryptingKey : NonceBased {
|
||||
interface DecryptingKey : NonceBased, KeyInstance {
|
||||
/**
|
||||
* Authenticated decryption that checks the message is not tampered and therefor
|
||||
* the key is valid. It is not possible in general to distinguish whether the key is invalid
|
||||
@ -28,8 +28,6 @@ interface DecryptingKey : NonceBased {
|
||||
|
||||
fun decryptString(cipherData: UByteArray): String = decrypt(cipherData).decodeFromUByteArray()
|
||||
|
||||
val id: KeyId
|
||||
|
||||
}
|
||||
|
||||
inline fun <reified T>DecryptingKey.decryptObject(cipherData: UByteArray): T =
|
||||
|
@ -11,7 +11,7 @@ import net.sergeych.crypto2.SymmetricKey.WithNonce
|
||||
* It is not serializable by design.
|
||||
* Custom implementations are, see [SymmetricKey] for example.
|
||||
*/
|
||||
interface EncryptingKey : NonceBased {
|
||||
interface EncryptingKey : NonceBased, KeyInstance {
|
||||
/**
|
||||
* Authenticated encrypting with optional random fill to protect from message size analysis.
|
||||
* Note that [randomFill] if present should be positive.
|
||||
@ -27,8 +27,6 @@ interface EncryptingKey : NonceBased {
|
||||
fun encrypt(plainText: String,randomFill: IntRange? = null): UByteArray =
|
||||
encrypt(plainText.encodeToUByteArray(),randomFill)
|
||||
|
||||
val id: KeyId
|
||||
|
||||
fun encryptWithNonce(plainData: UByteArray, nonce: UByteArray, randomFill: IntRange? = null): UByteArray
|
||||
}
|
||||
|
||||
|
14
src/commonMain/kotlin/net/sergeych/crypto2/KeyInstance.kt
Normal file
14
src/commonMain/kotlin/net/sergeych/crypto2/KeyInstance.kt
Normal file
@ -0,0 +1,14 @@
|
||||
package net.sergeych.crypto2
|
||||
|
||||
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)
|
||||
}
|
9
src/commonMain/kotlin/net/sergeych/crypto2/SigningKey.kt
Normal file
9
src/commonMain/kotlin/net/sergeych/crypto2/SigningKey.kt
Normal file
@ -0,0 +1,9 @@
|
||||
package net.sergeych.crypto2
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
interface SigningKey: KeyInstance {
|
||||
val verifyingKey: SigningPublicKey
|
||||
fun sign(message: UByteArray): UByteArray
|
||||
fun seal(message: UByteArray, expiresAt: Instant? = null): Seal
|
||||
}
|
@ -4,8 +4,18 @@ 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
|
||||
|
||||
@Serializable
|
||||
@ -84,17 +94,18 @@ sealed class UniversalKey {
|
||||
|
||||
|
||||
companion object {
|
||||
fun from(key: DecryptingKey): UniversalKey =
|
||||
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.new())
|
||||
Secret(Asymmetric.newSecretKey())
|
||||
fun newSigningKey(): Signing =
|
||||
Signing(SigningSecretKey.new())
|
||||
@Suppress("unused")
|
||||
|
@ -2,6 +2,17 @@ package net.sergeych.crypto2
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Keyring capable of holding [UniversalKey] instances (serializable keys of different types)
|
||||
* associated with a set of string tags. Rings are used to decrypt [Container], to store
|
||||
* and find keys, etc.
|
||||
*
|
||||
* It is _immutable_ so it is safe to share id with no precautions on possible RC. Every
|
||||
* function that modifies the ring returns a _new instance_ of it.
|
||||
*
|
||||
* __important__ serialized keyring is not protected and can disclose keys. Encrypt it after
|
||||
* serializing before sending over the network or store on media.
|
||||
*/
|
||||
@Serializable
|
||||
class UniversalRing(
|
||||
val keyWithTags: Map<UniversalKey, Set<String>>,
|
||||
@ -11,19 +22,44 @@ class UniversalRing(
|
||||
constructor(vararg keyTags: Pair<UniversalKey, String>)
|
||||
: this(keyTags.associate { it.first to setOf(it.second) })
|
||||
|
||||
val decryptingKeys: Set<DecryptingKey> by lazy { keys<DecryptingKey>() }
|
||||
/**
|
||||
* Only decrypting keys. This is a shortcut for [keysOfType]:
|
||||
* `keysOfType<DecryptingKey>()`.
|
||||
*/
|
||||
val decryptingKeys: Set<DecryptingKey> by lazy { keysOfType<DecryptingKey>() }
|
||||
|
||||
/**
|
||||
* Select keys of the specified type
|
||||
* Select all keys of the specified type. To select all keys, use [allKeys].
|
||||
*/
|
||||
inline fun <reified T> keys(): Set<T> =
|
||||
inline fun <reified T: KeyInstance> keysOfType(): Set<T> =
|
||||
allKeys.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 }
|
||||
|
||||
/**
|
||||
* 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].
|
||||
*
|
||||
* Please avoid selecting parameters that make possible to pick more than one key, it will cause an exception.
|
||||
*
|
||||
* @return found key by id of the specified type or null
|
||||
* @throws IllegalArgumentException if there is more than one keys matching the criteria
|
||||
*/
|
||||
inline fun <reified T: KeyInstance> findKey(id: KeyId): T? {
|
||||
val kk = allKeys.filter { it is T && it.id == id }
|
||||
if( kk.size > 1 )
|
||||
throw IllegalArgumentException(
|
||||
"ambiguous type selector ${T::class::simpleName}: ${kk.size} instances found, at most 1 allowed"
|
||||
)
|
||||
return kk.firstOrNull() as T?
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all keys for the specified id (normally it could be 0, 1 or 2). See [KeyId] about
|
||||
* matching id keys.
|
||||
*/
|
||||
fun keysById(id: KeyId): List<UniversalKey> = allKeys.filter { it.id == id }
|
||||
|
||||
/**
|
||||
@ -40,8 +76,12 @@ class UniversalRing(
|
||||
*/
|
||||
inline fun <reified T> keyByTag(tag: String) = keysByTags(tag).first { it is T }
|
||||
|
||||
/**
|
||||
* Get keys of the specified type having any of the specified tags associated.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
inline fun <reified T> keyByAnyTag(vararg tags: String) = keysByTags(*tags).first { it is T }
|
||||
inline fun <reified T> keysByAnyTag(vararg tags: String): Sequence<UniversalKey> =
|
||||
keysByTags(*tags).filter { it is T }
|
||||
|
||||
/**
|
||||
* Get all keys with a given id. Note that _matching keys_ have the same id, see [KeyId] for more.
|
||||
@ -72,7 +112,7 @@ class UniversalRing(
|
||||
* both rings.
|
||||
*/
|
||||
operator fun plus(other: UniversalRing): UniversalRing {
|
||||
var result = keyWithTags.toMutableMap()
|
||||
val result = keyWithTags.toMutableMap()
|
||||
for (e in other.keyWithTags.entries) {
|
||||
result[e.key]?.let {
|
||||
result[e.key] = it + e.value
|
||||
@ -158,6 +198,7 @@ class UniversalRing(
|
||||
/**
|
||||
* Create string "report" of the ring contents. Note it has no trailing `\n`
|
||||
*/
|
||||
@Suppress("unused")
|
||||
fun ls(): String {
|
||||
val result = mutableListOf<String>()
|
||||
for( e in keyWithTags.entries) {
|
||||
@ -182,8 +223,19 @@ class UniversalRing(
|
||||
return keyRings.reduce { l, r -> l + r }
|
||||
}
|
||||
|
||||
fun from(vararg keys: DecryptingKey): 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() } )
|
||||
|
||||
/**
|
||||
* 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) } )
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
package net.sergeych.crypto2
|
||||
|
||||
interface VerifyingKey: KeyInstance {
|
||||
/**
|
||||
* Verify the signature and return true if it is correct.
|
||||
*/
|
||||
fun verify(signature: UByteArray, message: UByteArray): Boolean
|
||||
}
|
@ -195,9 +195,9 @@ class KeysTest {
|
||||
assertEquals(usy2, usy1)
|
||||
assertFalse { usy1 == usy3 }
|
||||
|
||||
val sk1 = Asymmetric.new()
|
||||
val sk1 = Asymmetric.newSecretKey()
|
||||
val sk2 = Asymmetric.SecretKey(sk1.keyBytes)
|
||||
val sk3 = Asymmetric.new()
|
||||
val sk3 = Asymmetric.newSecretKey()
|
||||
|
||||
assertEquals(sk1, sk2)
|
||||
assertEquals(sk2, sk1)
|
||||
|
@ -16,7 +16,7 @@ class RingTest {
|
||||
val y2 = SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray())
|
||||
assertEquals(y1, y2)
|
||||
|
||||
val e1 = Asymmetric.new()
|
||||
val e1 = Asymmetric.newSecretKey()
|
||||
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.new())
|
||||
val k3 = UniversalKey.from(Asymmetric.new())
|
||||
val k2 = UniversalKey.from(Asymmetric.newSecretKey())
|
||||
val k3 = UniversalKey.from(Asymmetric.newSecretKey())
|
||||
//
|
||||
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.new().keyBytes.size)
|
||||
println(BipackEncoder.encode(Asymmetric.new()).size)
|
||||
println(Asymmetric.newSecretKey().keyBytes.size)
|
||||
println(BipackEncoder.encode(Asymmetric.newSecretKey()).size)
|
||||
val encoded = BipackEncoder.encode(r)
|
||||
println(encoded.toDump())
|
||||
println(encoded.size)
|
||||
@ -56,12 +56,28 @@ class RingTest {
|
||||
assertEquals(r, r2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAsymmetricPublic() = runTest {
|
||||
initCrypto()
|
||||
val sk = Asymmetric.newSecretKey()
|
||||
assertEquals(sk.id, sk.publicKey.id)
|
||||
val r = UniversalRing.from(sk.publicKey to "foo")
|
||||
println(sk.publicKey.id)
|
||||
println(sk.id)
|
||||
println(r.findKey<EncryptingKey>(sk.id))
|
||||
println(BipackEncoder.encode(r).toDump())
|
||||
val r1 = deepCopy(r)
|
||||
println(r1.findKey<EncryptingKey>(sk.id))
|
||||
println(sk)
|
||||
assertTrue { sk.publicKey.toUniversalKey() == r1.findKey<EncryptingKey>(sk.id) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTags() = runTest {
|
||||
initCrypto()
|
||||
|
||||
val k1 = UniversalKey.from(SymmetricKey("1234567890Hello,dolly.here-we-go".encodeToUByteArray()))
|
||||
val k2 = UniversalKey.from(Asymmetric.new())
|
||||
val k2 = UniversalKey.from(Asymmetric.newSecretKey())
|
||||
|
||||
val r1 = UniversalRing(k1, k2)
|
||||
var r2 = UniversalRing(deepCopy(k1), deepCopy(k2))
|
||||
|
Loading…
x
Reference in New Issue
Block a user