redesigned signed box now Sealed box, refactored seals to incorporate expiration, introduced symmetric keys support
This commit is contained in:
parent
b11ea4d35a
commit
aad44c5af5
@ -16,6 +16,9 @@ repositories {
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
js {
|
||||
browser()
|
||||
}
|
||||
linuxX64()
|
||||
linuxArm64()
|
||||
|
||||
@ -25,7 +28,8 @@ kotlin {
|
||||
// iosArm64()
|
||||
// iosSimulatorArm64()
|
||||
mingwX64()
|
||||
// wasmJs() no libsodimu bindings yet (strangely)
|
||||
// @OptIn(ExperimentalWasmDsl::class)
|
||||
// wasmJs() //no libsodium bindings yet (strangely)
|
||||
// val ktor_version = "2.3.6"
|
||||
|
||||
sourceSets {
|
||||
@ -41,9 +45,8 @@ kotlin {
|
||||
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")
|
||||
implementation("com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings:0.9.2")
|
||||
api("com.ionspin.kotlin:bignum:0.3.9")
|
||||
|
||||
api("net.sergeych:mp_bintools:0.1.5-SNAPSHOT")
|
||||
api("net.sergeych:mp_stools:1.4.1")
|
||||
}
|
||||
|
24
src/commonMain/kotlin/net/sergeych/crypto2/DecryptingKey.kt
Normal file
24
src/commonMain/kotlin/net/sergeych/crypto2/DecryptingKey.kt
Normal file
@ -0,0 +1,24 @@
|
||||
package net.sergeych.crypto2
|
||||
|
||||
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
|
||||
import net.sergeych.bipack.BipackDecoder
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
/**
|
||||
* 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
|
||||
* or the encrypted data is tampered so the only one exception is used.
|
||||
*
|
||||
* @throws DecryptionFailedException if the key is not valid or [cipherData] tampered.
|
||||
*/
|
||||
fun decrypt(cipherData: UByteArray): UByteArray
|
||||
|
||||
fun decryptString(cipherData: UByteArray): String = decrypt(cipherData).decodeFromUByteArray()
|
||||
}
|
||||
|
||||
inline fun <reified T>DecryptingKey.decryptObject(cipherData: UByteArray): T =
|
||||
BipackDecoder.decode<T>(decrypt(cipherData).toByteArray())
|
25
src/commonMain/kotlin/net/sergeych/crypto2/EncryptingKey.kt
Normal file
25
src/commonMain/kotlin/net/sergeych/crypto2/EncryptingKey.kt
Normal file
@ -0,0 +1,25 @@
|
||||
package net.sergeych.crypto2
|
||||
|
||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||
import net.sergeych.bipack.BipackEncoder
|
||||
|
||||
/**
|
||||
* Some key able to encrypt data with optional random fill that conceals message size
|
||||
* when needed.
|
||||
*
|
||||
* It is not serializable by design.
|
||||
* Custom implementations are, see [SymmetricKey] for example.
|
||||
*/
|
||||
interface EncryptingKey {
|
||||
/**
|
||||
* Authenticated encrypting with optional random fill to protect from message size analysis.
|
||||
* Note that [randomFill] if present should be positive.
|
||||
*/
|
||||
fun encrypt(plainData: UByteArray,randomFill: IntRange?=null): UByteArray
|
||||
|
||||
fun encrypt(plainText: String,randomFill: IntRange? = null): UByteArray =
|
||||
encrypt(plainText.encodeToUByteArray(),randomFill)
|
||||
}
|
||||
|
||||
inline fun <reified T>EncryptingKey.encryptObject(value: T,randomFill: IntRange? = null): UByteArray =
|
||||
encrypt(BipackEncoder.encode(value).toUByteArray(),randomFill)
|
@ -3,20 +3,44 @@ package net.sergeych.crypto2
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.sergeych.bipack.BipackEncoder
|
||||
import net.sergeych.bipack.decodeFromBipack
|
||||
import net.sergeych.crypto2.Seal.Companion.create
|
||||
import net.sergeych.utools.now
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextUBytes
|
||||
|
||||
/**
|
||||
* Extended public-key signature.
|
||||
*
|
||||
* See [Seal.create] for details and usage.
|
||||
*
|
||||
* This constructor normally should not be used directly, please use [Seal.create] instead
|
||||
*
|
||||
* @param publicKey the public key that could be used to verify the message. It could safely be published.
|
||||
* @param signature the signature generated by [create]. Do not generate it yourself.
|
||||
* @param nonce if [create] was called with `isDeterministic = true` there will be a random nonce.
|
||||
* that makes it impossible to judge whether known seals are related to the same unknown message.
|
||||
* @param createdAt creation time as specified when creating.
|
||||
* @param expiresAt if set by creator, determines the time instant from when the seal is invalid.
|
||||
*/
|
||||
@Serializable
|
||||
class Seal(
|
||||
val publicKey: SigningKey.Public,
|
||||
val signature: UByteArray,
|
||||
val nonce: UByteArray?,
|
||||
val createdAt: Instant,
|
||||
val expiresAt: Instant? = null,
|
||||
val expiresAt: Instant?,
|
||||
) {
|
||||
|
||||
/**
|
||||
* @suppress
|
||||
* This is the structure that is actually signed/verified with a key.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@Serializable
|
||||
class SealedData(
|
||||
val message: UByteArray,
|
||||
val nonce: UByteArray?,
|
||||
val createdAt: Instant?,
|
||||
val validUntil: Instant?,
|
||||
)
|
||||
@ -35,10 +59,13 @@ class Seal(
|
||||
* 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.
|
||||
*
|
||||
* It checks first the signature. _If it is ok, it also checks_ [expiresAt].
|
||||
*
|
||||
* See [check] and [isValid] for non-throwing checks.
|
||||
*
|
||||
* @throws ExpiredSignatureException
|
||||
* @throws IllegalSignatureException
|
||||
* @throws IllegalSignatureException if the signature is not valid for this [message]
|
||||
* @throws ExpiredSignatureException if the signature is valid, but [expiresAt] is not null and the seal
|
||||
* is expired.
|
||||
*/
|
||||
fun verify(message: UByteArray) {
|
||||
val n = now()
|
||||
@ -46,19 +73,80 @@ class Seal(
|
||||
expiresAt?.let {
|
||||
if (n >= it) throw ExpiredSignatureException("signature expired at $it")
|
||||
}
|
||||
val data = BipackEncoder.encode(SealedData(message, createdAt, expiresAt))
|
||||
val data = BipackEncoder.encode(SealedData(message, nonce, createdAt, expiresAt))
|
||||
if (!publicKey.verify(signature, data.toUByteArray()))
|
||||
throw IllegalSignatureException()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the seal is not expired.
|
||||
* __It is important__ that you can't determine whether the seal
|
||||
* is genuine and [expiresAt] not tampered if you do not have a message. If you do, be sure to call
|
||||
* [verify] or check [isValid] before calling this method.
|
||||
*/
|
||||
fun isExpired(): Boolean = expiresAt?.let { it > now() } != false
|
||||
|
||||
/**
|
||||
* In the rare cases you want to use it alone, the packed binary representation, use [unpack] to restore.
|
||||
* Most often, all you need is to put your Seal inside some kotlinx-serializable class.
|
||||
*/
|
||||
val packed: UByteArray by lazy { BipackEncoder.encode(this).toUByteArray() }
|
||||
|
||||
companion object {
|
||||
operator fun invoke(
|
||||
key: SigningKey.Secret, message: UByteArray,
|
||||
/**
|
||||
* 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, [SigningKey.Public]
|
||||
* instance, using public-key signing algorithms.
|
||||
*
|
||||
* Unlike a regular binary signature, Seal contains the signer's [publicKey], and also
|
||||
* [createdAt] and [expiresAt] fields which are also signed and are guaranteed to be non-tampered
|
||||
* if the [isValid] returns true (or [verify] does not throw). See [isExpired].
|
||||
*
|
||||
* It is important to understand that the seal itself could not be checked _having no message
|
||||
* it was created for_.
|
||||
* This is made intentionally: if you have no message, what means you are not the intended recipient, you can't
|
||||
* analyze the seal.
|
||||
*
|
||||
* To check that the message is genuine using a seal use:
|
||||
*
|
||||
* - [verify] which throws [IllegalSignatureException] or [ExpiredSignatureException] if it is not
|
||||
* - [check] that returns success or the exception without throwing it
|
||||
* - [isValid] that returns true or false, so you can't judge what was the reason (invalid signature or
|
||||
* expiration, do not check for expiration _after_ false is returned as you can't trust
|
||||
* into your seal yet).
|
||||
*
|
||||
* When you need to have a message and one or more seals all together, use [SealedBox].
|
||||
*
|
||||
* Please note for in the very rare key you want to trust the Seal having no message you need to create
|
||||
* another Seal to seal the seal. It sounds crazy as it is, and you should avoid such designs.
|
||||
*
|
||||
*
|
||||
* @param key secret key to sign with
|
||||
* @param message message to seal
|
||||
* @param createdAt seal creation time, usually current time
|
||||
* @param expiresAt optional seal expiration time
|
||||
* @param nonDeterministic if true, it is not possible to check whether two seals correspond to the same
|
||||
* unknown message (if the message is known, it is trivial by verifying the seal). It is a
|
||||
* rare case so default os false.
|
||||
*/
|
||||
fun create(
|
||||
key: SigningKey.Secret,
|
||||
message: UByteArray,
|
||||
createdAt: Instant = now(),
|
||||
expiresAt: Instant? = null,
|
||||
nonDeterministic: Boolean = false
|
||||
): Seal {
|
||||
val data = BipackEncoder.encode(SealedData(message, createdAt, expiresAt)).toUByteArray()
|
||||
return Seal(key.publicKey, key.sign(data), createdAt, expiresAt)
|
||||
val nonce = if( nonDeterministic ) Random.nextUBytes(32) else null
|
||||
val data = BipackEncoder.encode(SealedData(message, nonce, createdAt, expiresAt)).toUByteArray()
|
||||
return Seal(key.publicKey, key.sign(data), nonce, createdAt, expiresAt)
|
||||
}
|
||||
|
||||
/**
|
||||
* Int the rare case you need a packed seal alone, unpack it. Normally just add seal to some [Serializable]
|
||||
* class, it is serializable.
|
||||
*/
|
||||
fun unpack(packed: UByteArray): Seal = packed.toByteArray().decodeFromBipack()
|
||||
}
|
||||
}
|
@ -1,17 +1,19 @@
|
||||
package net.sergeych.crypto2
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
|
||||
/**
|
||||
* Multi-signed data box. Use [SignedBox.invoke] to easily create
|
||||
* instances and [SignedBox.plus] to add more signatures (signing keys), and
|
||||
* [SignedBox.contains] to check for a specific key signature presence.
|
||||
* Multi-signed data box. Do not use the constructori directly, use [SealedBox.create]
|
||||
* instead to create the box and [SealedBox.plus] or [addSeal] to add more signatures
|
||||
* with different signing keys.
|
||||
* [SealedBox.contains] checks 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
|
||||
* It is serializable and checks integrity __on deserialization__k. 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
|
||||
* signers.
|
||||
@ -20,7 +22,7 @@ import kotlinx.serialization.Transient
|
||||
* know what you are doing as it may be dangerous.Use one of the above to create or change it.
|
||||
*/
|
||||
@Serializable
|
||||
class SignedBox(
|
||||
class SealedBox(
|
||||
val message: UByteArray,
|
||||
private val seals: List<Seal>,
|
||||
@Transient
|
||||
@ -32,9 +34,18 @@ class SignedBox(
|
||||
* 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: SigningKey.Secret): SignedBox =
|
||||
operator fun plus(key: SigningKey.Secret): SealedBox =
|
||||
if (key.publicKey in this) this
|
||||
else SignedBox(message, seals + key.seal(message),false)
|
||||
else SealedBox(message, seals + key.seal(message),false)
|
||||
|
||||
/**
|
||||
* Add expiring seal, otherwise use [plus]. Overrides exising seal for [key]
|
||||
* if present:
|
||||
*/
|
||||
fun addSeal(key: SigningKey.Secret,expresAt: Instant): SealedBox {
|
||||
val filtered = seals.filter { it.publicKey != key.publicKey }
|
||||
return SealedBox(message, filtered + key.seal(message, expresAt), false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that it is signed with a specified key.
|
||||
@ -55,15 +66,15 @@ class SignedBox(
|
||||
/**
|
||||
* Create a new instance with a specific data sealed by one or more
|
||||
* keys. At least one key is required to disallow providing not-signed
|
||||
* instances, e.g. [SignedBox] is guaranteed to be properly sealed when
|
||||
* instances, e.g. [SealedBox] is guaranteed to be properly sealed when
|
||||
* successfully instantiated.
|
||||
*
|
||||
* @param data a message to sign
|
||||
* @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 {
|
||||
return SignedBox(data, keys.map { it.seal(data) }, false)
|
||||
fun create(data: UByteArray, vararg keys: SigningKey.Secret): SealedBox {
|
||||
return SealedBox(data, keys.map { it.seal(data) }, false)
|
||||
}
|
||||
}
|
||||
}
|
@ -62,8 +62,8 @@ sealed class SigningKey {
|
||||
|
||||
fun sign(message: UByteArray): UByteArray = Signature.detached(message, packed)
|
||||
|
||||
fun seal(message: UByteArray, validUntil: Instant? = null): Seal =
|
||||
Seal(this, message, now(), validUntil)
|
||||
fun seal(message: UByteArray, expiresAt: Instant? = null): Seal =
|
||||
Seal.create(this, message, now(), expiresAt)
|
||||
|
||||
override fun toString(): String = "Sct:${super.toString()}"
|
||||
|
||||
|
79
src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt
Normal file
79
src/commonMain/kotlin/net/sergeych/crypto2/SymmetricKey.kt
Normal file
@ -0,0 +1,79 @@
|
||||
package net.sergeych.crypto2
|
||||
|
||||
import com.ionspin.kotlin.crypto.secretbox.SecretBox
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.sergeych.bintools.toDataSource
|
||||
import net.sergeych.bipack.BipackDecoder
|
||||
import net.sergeych.bipack.BipackEncoder
|
||||
import net.sergeych.crypto2.SymmetricKey.Companion.random
|
||||
import kotlin.random.Random
|
||||
import kotlin.random.nextInt
|
||||
import kotlin.random.nextUBytes
|
||||
|
||||
/**
|
||||
* Symmetric key implements authenticated encrypting with random nonce and optional fill.
|
||||
* Random fill is normally used when cryptanalysis of the message size is a threat.
|
||||
*
|
||||
* Do not call this constructor directly, use [random] or deserialize it.
|
||||
*
|
||||
* __Algorithms:__
|
||||
*
|
||||
* - Encryption: XSalsa20 stream cipher.
|
||||
* - Authentication: Poly1305 MAC
|
||||
*/
|
||||
@Serializable
|
||||
class SymmetricKey(
|
||||
val keyBytes: UByteArray
|
||||
): EncryptingKey, DecryptingKey {
|
||||
|
||||
/**
|
||||
* @suppress
|
||||
* nonce + ciphered data serialization aid
|
||||
*/
|
||||
@Serializable
|
||||
data class WithNonce(
|
||||
val cipherData: UByteArray,
|
||||
val nonce: UByteArray,
|
||||
)
|
||||
|
||||
/**
|
||||
* @suppress
|
||||
* add some random data to the end of the plain message
|
||||
*/
|
||||
@Serializable
|
||||
data class WithFill(
|
||||
val data: UByteArray,
|
||||
val safetyFill: UByteArray? = null
|
||||
)
|
||||
|
||||
override fun encrypt(plainData: UByteArray,randomFill: IntRange?): UByteArray {
|
||||
val fill = randomFill?.let {
|
||||
require(it.start >= 0)
|
||||
Random.nextUBytes(Random.nextInt(it))
|
||||
}
|
||||
val filled = BipackEncoder.encode(WithFill(plainData, fill))
|
||||
val nonce = randomNonce()
|
||||
val encrypted = SecretBox.easy(filled.toUByteArray(), nonce, keyBytes)
|
||||
return BipackEncoder.encode(WithNonce(encrypted, nonce)).toUByteArray()
|
||||
}
|
||||
|
||||
override fun decrypt(cipherData: UByteArray): UByteArray {
|
||||
val wn: WithNonce = BipackDecoder.Companion.decode(cipherData.toDataSource())
|
||||
try {
|
||||
return BipackDecoder.Companion.decode<WithFill>(
|
||||
SecretBox.openEasy(wn.cipherData, wn.nonce, keyBytes).toDataSource()
|
||||
).data
|
||||
}
|
||||
catch(_: com.ionspin.kotlin.crypto.secretbox.SecretBoxCorruptedOrTamperedDataExceptionOrInvalidKey) {
|
||||
throw DecryptionFailedException()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create a secure random symmetric key.
|
||||
*/
|
||||
fun random() = SymmetricKey(SecretBox.keygen())
|
||||
}
|
||||
|
||||
}
|
@ -2,30 +2,12 @@
|
||||
|
||||
package net.sergeych.crypto2
|
||||
|
||||
import com.ionspin.kotlin.crypto.secretbox.SecretBox
|
||||
import com.ionspin.kotlin.crypto.secretbox.crypto_secretbox_NONCEBYTES
|
||||
import com.ionspin.kotlin.crypto.util.LibsodiumRandom
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.sergeych.bintools.toDataSource
|
||||
import net.sergeych.bipack.BipackDecoder
|
||||
import net.sergeych.bipack.BipackEncoder
|
||||
|
||||
class DecryptionFailedException : RuntimeException("can't encrypt: wrong key or tampered message")
|
||||
|
||||
@Serializable
|
||||
data class WithNonce(
|
||||
val cipherData: UByteArray,
|
||||
val nonce: UByteArray,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class WithFill(
|
||||
val data: UByteArray,
|
||||
val safetyFill: UByteArray? = null
|
||||
) {
|
||||
constructor(data: UByteArray, fillSize: Int) : this(data, randomBytes(fillSize))
|
||||
}
|
||||
|
||||
suspend fun readVarUnsigned(input: ReceiveChannel<UByte>): UInt {
|
||||
var result = 0u
|
||||
@ -73,39 +55,4 @@ fun <T: Comparable<T>>T.limitMin(min: T) = if( this > min ) this else min
|
||||
|
||||
fun randomNonce(): UByteArray = randomBytes(crypto_secretbox_NONCEBYTES)
|
||||
|
||||
/**
|
||||
* Secret-key encrypt with authentication.
|
||||
* Generates random nonce and add some random fill to protect
|
||||
* against some analysis attacks. Nonce is included in the result. To be
|
||||
* used with [decrypt].
|
||||
* @param secretKey a _secret_ key, see [SecretBox.keygen()] or like.
|
||||
* @param plain data to encrypt
|
||||
* @param fillSize number of random fill data to add. Use random value or default.
|
||||
*/
|
||||
fun encrypt(
|
||||
secretKey: UByteArray,
|
||||
plain: UByteArray,
|
||||
fillSize: Int = randomUInt((plain.size * 3 / 10).limitMin(3)).toInt()
|
||||
): UByteArray {
|
||||
val filled = BipackEncoder.encode(WithFill(plain, fillSize))
|
||||
val nonce = randomNonce()
|
||||
val encrypted = SecretBox.easy(filled.toUByteArray(), nonce, secretKey)
|
||||
return BipackEncoder.encode(WithNonce(encrypted, nonce)).toUByteArray()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a secret-key-based message, normally encrypted with [encrypt].
|
||||
* @throws DecryptionFailedException if the key is wrong or a message is tampered with (MAC
|
||||
* check failed).
|
||||
*/
|
||||
fun decrypt(secretKey: UByteArray, cipher: UByteArray): UByteArray {
|
||||
val wn: WithNonce = BipackDecoder.decode(cipher.toDataSource())
|
||||
try {
|
||||
return BipackDecoder.decode<WithFill>(
|
||||
SecretBox.openEasy(wn.cipherData, wn.nonce, secretKey).toDataSource()
|
||||
).data
|
||||
}
|
||||
catch(_: com.ionspin.kotlin.crypto.secretbox.SecretBoxCorruptedOrTamperedDataExceptionOrInvalidKey) {
|
||||
throw DecryptionFailedException()
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
import com.ionspin.kotlin.crypto.secretbox.SecretBox
|
||||
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
|
||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.crypto2.*
|
||||
import net.sergeych.utools.now
|
||||
import net.sergeych.utools.pack
|
||||
import net.sergeych.utools.unpack
|
||||
import kotlin.test.*
|
||||
|
||||
class KeysTest {
|
||||
@Test
|
||||
fun testCreationAndMap() = runTest {
|
||||
fun testSigningCreationAndMap() = runTest {
|
||||
initCrypto()
|
||||
val (stk,pbk) = SigningKey.pair()
|
||||
|
||||
@ -30,28 +30,56 @@ class KeysTest {
|
||||
val p2 = SigningKey.pair()
|
||||
val p3 = SigningKey.pair()
|
||||
|
||||
val ms = SignedBox(data, s1) + p2.secretKey
|
||||
val ms = SealedBox.create(data, s1) + p2.secretKey
|
||||
|
||||
// non tampered:
|
||||
val ms1 = unpack<SignedBox>(pack(ms))
|
||||
val ms1 = unpack<SealedBox>(pack(ms))
|
||||
assertContentEquals(data, ms1.message)
|
||||
assertTrue(pbk in ms1)
|
||||
assertTrue(p2.publicKey in ms1)
|
||||
assertTrue(p3.publicKey !in ms1)
|
||||
|
||||
assertThrows<IllegalSignatureException> {
|
||||
unpack<SignedBox>(pack(ms).also { it[3] = 1u })
|
||||
unpack<SealedBox>(pack(ms).also { it[3] = 1u })
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNonDeterministicSeals() = runTest {
|
||||
initCrypto()
|
||||
val data = "Welcome to the Miami, bitch!".encodeToUByteArray()
|
||||
val (sk,_) = SigningKey.pair()
|
||||
val t = now()
|
||||
val s1 = Seal.create(sk, data, createdAt = t)
|
||||
val s2 = Seal.create(sk, data, createdAt = t)
|
||||
val s2bad = Seal.create(sk, data + "!".encodeToUByteArray())
|
||||
val s3 = Seal.create(sk, data, createdAt = t, nonDeterministic = true)
|
||||
val s4 = Seal.create(sk, data, createdAt = t, nonDeterministic = true)
|
||||
|
||||
for( seal in listOf(s1,s2,s3,s4)) {
|
||||
assertTrue { seal.isValid(data) }
|
||||
assertTrue { Seal.unpack(seal.packed).isValid(data) }
|
||||
}
|
||||
assertFalse { s2bad.isValid(data)}
|
||||
assertContentEquals(s1.packed, s2.packed)
|
||||
assertFalse { s1.packed contentEquals s3.packed }
|
||||
assertFalse { s4.packed contentEquals s3.packed }
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun secretEncryptTest() = runTest {
|
||||
initCrypto()
|
||||
val key = SecretBox.keygen()
|
||||
val key1 = SecretBox.keygen()
|
||||
assertEquals("hello", decrypt(key, encrypt(key, "hello".encodeToUByteArray())).decodeFromUByteArray())
|
||||
val key = SymmetricKey.random()
|
||||
val key1 = SymmetricKey.random()
|
||||
assertEquals("hello", key.decrypt(key.encrypt("hello".encodeToUByteArray())).decodeFromUByteArray())
|
||||
assertEquals("hello", key.decryptString(key.encrypt("hello")))
|
||||
assertEquals("hello", key.decryptObject(key.encryptObject("hello")))
|
||||
assertEquals("hello", key.decrypt(key.encrypt("hello".encodeToUByteArray(), 18..334)).decodeFromUByteArray())
|
||||
assertEquals("hello", key.decryptString(key.encrypt("hello", 18..334)))
|
||||
assertEquals("hello", key.decryptObject(key.encryptObject("hello", 18..334)))
|
||||
assertThrows<DecryptionFailedException> {
|
||||
decrypt(key, encrypt(key1, "hello".encodeToUByteArray())).decodeFromUByteArray()
|
||||
key.decrypt(key1.encrypt("hello".encodeToUByteArray())).decodeFromUByteArray()
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user