migration to 2.0. New version with reinforced signed box to include timestamp and expiration

This commit is contained in:
sergeych committed 2024-06-08 16:19:13 +07:00
1 parent b86c7a853e
commit fe0cb59c51
9 files changed
+123 -50

No files matched your search

@@ -1,11 +1,64 @@
package net.sergeych.crypto2
import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable
import net.sergeych.bipack.BipackEncoder
import net.sergeych.utools.now
@Serializable
class Seal(
val publicKey: SigningKey.Public,
val signature: UByteArray
val signature: UByteArray,
val createdAt: Instant,
val expiresAt: Instant? = null,
) {
inline fun verify(message: UByteArray) = publicKey.verify(signature, message)
@Suppress("unused")
@Serializable
class SealedData(
val message: UByteArray,
val createdAt: Instant?,
val validUntil: Instant?,
)
/**
* Return true if the seal is correct, see [verify]
*/
fun isValid(message: UByteArray): Boolean = kotlin.runCatching { verify(message) }.isSuccess
/**
* Return Result containing success or error reason if the seal is not correct
*/
fun check(message: UByteArray): Result<Unit> = kotlin.runCatching { verify(message) }
/**
* Check that message is correct for this seal and throws exception if it is not.
* Note that tampering [createdAt] and [expiresAt] invalidate the seal too.
*
* See [check] and [isValid] for non-throwing checks.
*
* @throws ExpiredSignatureException
* @throws IllegalSignatureException
*/
fun verify(message: UByteArray) {
val n = now()
if (createdAt > n) throw IllegalSignatureException("signature's timestamp in the future")
expiresAt?.let {
if (n >= it) throw ExpiredSignatureException("signature expired at $it")
}
val data = BipackEncoder.encode(SealedData(message, createdAt, expiresAt))
if (!publicKey.verify(signature, data.toUByteArray()))
throw IllegalSignatureException()
}
companion object {
operator fun invoke(
key: SigningKey.Secret, message: UByteArray,
createdAt: Instant = now(),
expiresAt: Instant? = null,
): Seal {
val data = BipackEncoder.encode(SealedData(message, createdAt, expiresAt)).toUByteArray()
return Seal(key.publicKey, key.sign(data), createdAt, expiresAt)
}
}
}
@@ -8,6 +8,9 @@ import kotlinx.serialization.Transient
* instances and [SignedBox.plus] to add more signatures (signing keys), and
* [SignedBox.contains] to check for a specific key signature presence.
*
* Signatures, [Seal], incorporate creation time and optional expiration which are
* also signed and checked upon deserialization.
*
* It is serializable and checks integrity on deserialization. If any of seals does not
* match the signed [message], it throws [IllegalSignatureException] _on deserialization_.
* E.g., if you have it deserialized, it is ok, check it contains all needed keys among
@@ -31,7 +34,7 @@ class SignedBox(
*/
operator fun plus(key: SigningKey.Secret): SignedBox =
if (key.publicKey in this) this
else SignedBox(message, seals + key.seal(message), false)
else SignedBox(message, seals + key.seal(message),false)
/**
* Check that it is signed with a specified key.
@@ -43,7 +46,7 @@ class SignedBox(
init {
if (seals.isEmpty()) throw IllegalArgumentException("there should be at least one seal")
if (checkOnInit) {
if (!seals.all { it.verify(message) }) throw IllegalSignatureException()
for( s in seals ) s.verify(message)
}
}
@@ -59,7 +62,8 @@ class SignedBox(
* @param keys a list of keys to sign with, should be at least one key.
* @throws IllegalArgumentException if keys are not specified.
*/
operator fun invoke(data: UByteArray, vararg keys: SigningKey.Secret): SignedBox =
SignedBox(data, keys.map { it.seal(data) }, false)
operator fun invoke(data: UByteArray, vararg keys: SigningKey.Secret): SignedBox {
return SignedBox(data, keys.map { it.seal(data) }, false)
}
}
}
@@ -2,9 +2,11 @@ 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.SigningKey.Secret
import net.sergeych.crypto2.SigningKey.Companion.pair
import net.sergeych.utools.now
/**
* Keys in general: public, secret and later symmetric too.
@@ -60,7 +62,9 @@ sealed class SigningKey {
fun sign(message: UByteArray): UByteArray = Signature.detached(message, packed)
fun seal(message: UByteArray): Seal = Seal(this.publicKey, sign(message))
fun seal(message: UByteArray, validUntil: Instant? = null): Seal =
Seal(this, message, now(), validUntil)
override fun toString(): String = "Sct:${super.toString()}"
}
@@ -75,4 +79,7 @@ sealed class SigningKey {
}
}
class IllegalSignatureException: RuntimeException("signed data is tampered or signature is corrupted")
open class IllegalSignatureException(text: String="signed data is tampered or signature is corrupted")
: IllegalStateException(text)
class ExpiredSignatureException(text: String): IllegalSignatureException(text)