migration to 2.0. New version with reinforced signed box to include timestamp and expiration
This commit is contained in:
parent
b86c7a853e
commit
fe0cb59c51
6
.gitignore
vendored
6
.gitignore
vendored
@ -39,4 +39,8 @@ bin/
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
||||
# Other
|
||||
.kotlin
|
||||
.idea
|
||||
|
8
.idea/artifacts/crypto2_js_0_1_1_SNAPSHOT.xml
generated
Normal file
8
.idea/artifacts/crypto2_js_0_1_1_SNAPSHOT.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact type="jar" name="crypto2-js-0.1.1-SNAPSHOT">
|
||||
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||
<root id="archive" name="crypto2-js-0.1.1-SNAPSHOT.jar">
|
||||
<element id="module-output" name="crypto2.jsMain" />
|
||||
</root>
|
||||
</artifact>
|
||||
</component>
|
8
.idea/artifacts/crypto2_jvm_0_1_1_SNAPSHOT.xml
generated
Normal file
8
.idea/artifacts/crypto2_jvm_0_1_1_SNAPSHOT.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact type="jar" name="crypto2-jvm-0.1.1-SNAPSHOT">
|
||||
<output-path>$PROJECT_DIR$/build/libs</output-path>
|
||||
<root id="archive" name="crypto2-jvm-0.1.1-SNAPSHOT.jar">
|
||||
<element id="module-output" name="crypto2.jvmMain" />
|
||||
</root>
|
||||
</artifact>
|
||||
</component>
|
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -4,7 +4,7 @@
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="17 (5)" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17 (5)" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
@ -1,46 +1,35 @@
|
||||
import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform") version "1.9.20"
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.20"
|
||||
kotlin("multiplatform") version "2.0.0"
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "2.0.0"
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "0.1.1-SNAPSHOT"
|
||||
version = "0.2.1-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://maven.universablockchain.com/")
|
||||
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm {
|
||||
jvmToolchain(8)
|
||||
withJava()
|
||||
testRuns.named("test") {
|
||||
executionTask.configure {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
}
|
||||
js(KotlinJsCompilerType.IR) {
|
||||
browser {
|
||||
}
|
||||
}
|
||||
val hostOs = System.getProperty("os.name")
|
||||
val isArm64 = System.getProperty("os.arch") == "aarch64"
|
||||
val isMingwX64 = hostOs.startsWith("Windows")
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val nativeTarget = when {
|
||||
hostOs == "Mac OS X" && isArm64 -> macosArm64("native")
|
||||
hostOs == "Mac OS X" && !isArm64 -> macosX64("native")
|
||||
hostOs == "Linux" && isArm64 -> linuxArm64("native")
|
||||
hostOs == "Linux" && !isArm64 -> linuxX64("native")
|
||||
isMingwX64 -> mingwX64("native")
|
||||
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
|
||||
jvm()
|
||||
// {
|
||||
// jvmToolchain(8)
|
||||
// withJava()
|
||||
// testRuns.named("test") {
|
||||
// executionTask.configure {
|
||||
// useJUnitPlatform()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
js(IR) {
|
||||
browser()
|
||||
nodejs()
|
||||
}
|
||||
linuxX64("native")
|
||||
|
||||
val ktor_version = "2.3.6"
|
||||
|
||||
@ -53,13 +42,13 @@ kotlin {
|
||||
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.0")
|
||||
|
||||
implementation("com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings:0.9.0")
|
||||
api("com.ionspin.kotlin:bignum:0.3.8")
|
||||
|
||||
api("net.sergeych:mp_bintools:0.0.6")
|
||||
api("net.sergeych:mp_bintools:0.1.5-SNAPSHOT")
|
||||
api("net.sergeych:mp_stools:1.4.1")
|
||||
}
|
||||
}
|
||||
@ -80,11 +69,11 @@ kotlin {
|
||||
}
|
||||
}
|
||||
val jsTest by getting
|
||||
val nativeMain by getting {
|
||||
dependencies {
|
||||
}
|
||||
}
|
||||
val nativeTest by getting
|
||||
// val nativeMain by getting {
|
||||
// dependencies {
|
||||
// }
|
||||
// }
|
||||
// val nativeTest by getting
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
@ -23,10 +23,10 @@ class KeysTest {
|
||||
val data = "8 rays dev!".encodeToUByteArray()
|
||||
val data1 = "8 rays dev!".encodeToUByteArray()
|
||||
val s = stk.seal(data)
|
||||
assertTrue(s.verify(data))
|
||||
s.verify(data)
|
||||
|
||||
data1[0] = 0x01u
|
||||
assertFalse(s.verify(data1))
|
||||
assertFalse(s.isValid(data1))
|
||||
val p2 = SigningKey.pair()
|
||||
val p3 = SigningKey.pair()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user