All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jsMain.io.eqoty.secretk.crypto.AesSIV.kt Maven / Gradle / Ivy

package io.eqoty.secretk.crypto

import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray
import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array
import jslibs.miscreant.SIV
import kotlinx.coroutines.await
import kotlinx.coroutines.flow.*

val createWindowBroadcaster = MutableSharedFlow()
val windowIsSetup = MutableStateFlow(!js("typeof window == 'undefined'") as Boolean)
val destroyWindowBroadcaster = MutableSharedFlow()
val initiallyHadNoWindow = !windowIsSetup.value

actual class AesSIV {
    /**
     * This is a hacky workaround to make a window object only in our mocha tests.
     * (miscreant.SIV requires a window.crypto)
     * We create a window with happy-dom which we only want to include as a dev/test npm dependency.
     *
     * Note: For js tests I tried the logical method of setting up and destroying happy-dom using
     * @BeforeTest @AfterTest annotated functions but that does not work for some reason.
     * It causes the following error when an api call is made:
     *
     * TypeError: Cannot read properties of undefined (reading 'host_1')
     * at .applyOrigin(/Users/lucaspinazzola/Projects/secretk/URLBuilder.kt:105)
     * at URLBuilder.build_1k0s4u(/Users/lucaspinazzola/Projects/secretk/URLBuilder.kt:88)
     * at .Url(/Users/lucaspinazzola/Projects/secretk/URLUtils.kt:13)
     *
     */
    suspend fun awaitFakeWindow() {
        windowIsSetup.onSubscription {
            createWindowBroadcaster.emit(Unit)
        }.filter { it }.take(1).first()
    }

    suspend fun awaitDestroyFakeWindow() {
        windowIsSetup.onSubscription {
            destroyWindowBroadcaster.emit(Unit)
        }.filter { !it }.take(1).first()
    }

    actual suspend fun encrypt(
        txEncryptionKey: UByteArray,
        plaintext: UByteArray,
        associatedData: UByteArray
    ): UByteArray {
        if (!windowIsSetup.value) {
            awaitFakeWindow()
        }
        val key = SIV.importKey(txEncryptionKey.toUInt8Array(), "AES-SIV").await()
        val ciphertext =
            key.seal(plaintext.toUInt8Array(), arrayOf(associatedData.toUInt8Array())).await().toUByteArray()
        if (initiallyHadNoWindow) {
            awaitDestroyFakeWindow()
        }
        return ciphertext
    }

    actual suspend fun decrypt(
        txEncryptionKey: UByteArray,
        ciphertext: UByteArray,
        associatedData: UByteArray
    ): UByteArray {
        if (!windowIsSetup.value) {
            awaitFakeWindow()
        }
        val key = SIV.importKey(txEncryptionKey.toUInt8Array(), "AES-SIV").await()
        val plaintext =
            key.open(ciphertext.toUInt8Array(), arrayOf(associatedData.toUInt8Array())).await().toUByteArray()
        if (initiallyHadNoWindow) {
            awaitDestroyFakeWindow()
        }
        return plaintext
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy