forked from sergeych/crypto2
migration to 2.0. New version with reinforced signed box to include timestamp and expiration
This commit is contained in:
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)
|
||||
Reference in new issue
Block a user