diff --git a/buildSrc/src/main/kotlin/Deps.kt b/buildSrc/src/main/kotlin/Deps.kt index 709cd62..6022e06 100644 --- a/buildSrc/src/main/kotlin/Deps.kt +++ b/buildSrc/src/main/kotlin/Deps.kt @@ -72,6 +72,20 @@ object Deps { } + object wasmJs { + val stdLib = "stdlib-wasm" + val test = "test-wasm" + // TODO: написано от балды \/ +// val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:${Versions.kotlinCoroutines}" +// val serialization = "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:${Versions.kotlinSerialization}" + + + object Npm { + val libsodiumWrappers = Pair("libsodium-wrappers-sumo", "0.7.13") + + } + } + object Jvm { val stdLib = "stdlib-jdk8" val test = "test" diff --git a/multiplatform-crypto-libsodium-bindings/build.gradle.kts b/multiplatform-crypto-libsodium-bindings/build.gradle.kts index 2b39ae8..43158aa 100644 --- a/multiplatform-crypto-libsodium-bindings/build.gradle.kts +++ b/multiplatform-crypto-libsodium-bindings/build.gradle.kts @@ -92,6 +92,15 @@ kotlin { runningOnLinuxx86_64 { println("Configuring Linux X86-64 targets") + wasmJs { + browser { + testTask { + useKarma { + useChromeHeadless() + } + } + } + } js { browser { @@ -557,6 +566,19 @@ kotlin { runningOnLinuxx86_64 { println("Configuring Linux 64 Bit source sets") + val wasmJsMain by getting { + // TODO: разобраться (и с test) + dependencies { + implementation(kotlin(Deps.wasmJs.stdLib)) + implementation(npm(Deps.wasmJs.Npm.libsodiumWrappers.first, Deps.wasmJs.Npm.libsodiumWrappers.second)) + } + } + val wasmJsTest by getting { + dependencies { + implementation(kotlin(Deps.wasmJs.test)) + implementation(npm(Deps.wasmJs.Npm.libsodiumWrappers.first, Deps.wasmJs.Npm.libsodiumWrappers.second)) + } + } val jsMain by getting { dependencies { @@ -709,6 +731,14 @@ tasks { // } // } + // TODO: ваще не жс тест, помогите + val wasmJsBrowserTest by getting(KotlinJsTest::class) { + testLogging { + events("PASSED", "FAILED", "SKIPPED") + showStandardStreams = true + } + } + val jsBrowserTest by getting(KotlinJsTest::class) { testLogging { events("PASSED", "FAILED", "SKIPPED") diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/JsSodiumInterface.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/JsSodiumInterface.kt new file mode 100644 index 0000000..ed74443 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/JsSodiumInterface.kt @@ -0,0 +1,367 @@ +package ext.libsodium.com.ionspin.kotlin.crypto + + +import com.ionspin.kotlin.crypto.box.BoxKeyPair +import org.khronos.webgl.Uint8Array + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 27-May-2020 + */ +@JsModule("libsodium-wrappers-sumo") +@JsNonModule +external object JsSodiumInterface { + + + @JsName("crypto_generichash") + fun crypto_generichash(hashLength: Int, inputMessage: Uint8Array, key: Uint8Array): Uint8Array + + @JsName("crypto_hash_sha256") + fun crypto_hash_sha256(message: Uint8Array): Uint8Array + + @JsName("crypto_hash_sha512") + fun crypto_hash_sha512(message: Uint8Array): Uint8Array + + // ---- Generic hash ---- // Updateable + + @JsName("crypto_generichash_init") + fun crypto_generichash_init(key : Uint8Array, hashLength: Int) : dynamic + + @JsName("crypto_generichash_update") + fun crypto_generichash_update(state: dynamic, inputMessage: Uint8Array) + + @JsName("crypto_generichash_final") + fun crypto_generichash_final(state: dynamic, hashLength: Int) : Uint8Array + + @JsName("crypto_generichash_keygen") + fun crypto_generichash_keygen() : Uint8Array + + // ---- Generic hash end ---- // Updateable + + // ---- Blake2b ---- + + @JsName("crypto_generichash_blake2b") + fun crypto_generichash_blake2b(hashLength: Int, inputMessage: Uint8Array, key: Uint8Array): Uint8Array + + @JsName("crypto_generichash_blake2b_init") + fun crypto_generichash_blake2b_init(key : Uint8Array, hashLength: Int) : dynamic + + @JsName("crypto_generichash_blake2b_update") + fun crypto_generichash_blake2b_update(state: dynamic, inputMessage: Uint8Array) + + @JsName("crypto_generichash_blake2b_final") + fun crypto_generichash_blake2b_final(state: dynamic, hashLength: Int) : Uint8Array + + @JsName("crypto_generichash_blake2b_keygen") + fun crypto_generichash_blake2b_keygen() : Uint8Array + + // ---- Blake2b end ---- + + // ---- Short hash ---- + @JsName("crypto_shorthash") + fun crypto_shorthash(data : Uint8Array, key: Uint8Array) : Uint8Array + + @JsName("crypto_shorthash_keygen") + fun crypto_shorthash_keygen() : Uint8Array + // ---- Short hash end ---- + + + @JsName("crypto_hash_sha256_init") + fun crypto_hash_sha256_init() : dynamic + + @JsName("crypto_hash_sha256_update") + fun crypto_hash_sha256_update(state: dynamic, message: Uint8Array) + + @JsName("crypto_hash_sha256_final") + fun crypto_hash_sha256_final(state: dynamic): Uint8Array + + @JsName("crypto_hash_sha512_init") + fun crypto_hash_sha512_init() : dynamic + + @JsName("crypto_hash_sha512_update") + fun crypto_hash_sha512_update(state: dynamic, message: Uint8Array) + + @JsName("crypto_hash_sha512_final") + fun crypto_hash_sha512_final(state: dynamic): Uint8Array + + //XChaCha20Poly1305 - also in bindings + //fun crypto_aead_xchacha20poly1305_ietf_encrypt(message: Uint8Array, associatedData: Uint8Array, secretNonce: Uint8Array, nonce: Uint8Array, key: Uint8Array) : Uint8Array + //fun crypto_aead_xchacha20poly1305_ietf_decrypt(secretNonce: Uint8Array, ciphertext: Uint8Array, associatedData: Uint8Array, nonce: Uint8Array, key: Uint8Array) : Uint8Array + + //XChaCha20Poly1305 + //encrypt + @JsName("crypto_secretstream_xchacha20poly1305_init_push") + fun crypto_secretstream_xchacha20poly1305_init_push(key: Uint8Array) : dynamic + @JsName("crypto_secretstream_xchacha20poly1305_push") + fun crypto_secretstream_xchacha20poly1305_push(state: dynamic, message: Uint8Array, associatedData: Uint8Array, tag: UByte) : Uint8Array + + //decrypt + @JsName("crypto_secretstream_xchacha20poly1305_init_pull") + fun crypto_secretstream_xchacha20poly1305_init_pull(header: Uint8Array, key: Uint8Array) : dynamic + @JsName("crypto_secretstream_xchacha20poly1305_pull") + fun crypto_secretstream_xchacha20poly1305_pull(state: dynamic, ciphertext: Uint8Array, associatedData: Uint8Array) : dynamic + + //keygen and rekey + @JsName("crypto_secretstream_xchacha20poly1305_keygen") + fun crypto_secretstream_xchacha20poly1305_keygen() : Uint8Array + @JsName("crypto_secretstream_xchacha20poly1305_rekey") + fun crypto_secretstream_xchacha20poly1305_rekey(state: dynamic) + + // ---- SecretBox ---- + @JsName("crypto_secretbox_detached") + fun crypto_secretbox_detached(message: Uint8Array, nonce: Uint8Array, key: Uint8Array) : dynamic + @JsName("crypto_secretbox_easy") + fun crypto_secretbox_easy(message: Uint8Array, nonce: Uint8Array, key: Uint8Array) : Uint8Array + @JsName("crypto_secretbox_keygen") + fun crypto_secretbox_keygen() : Uint8Array + @JsName("crypto_secretbox_open_detached") + fun crypto_secretbox_open_detached(ciphertext : Uint8Array, tag : Uint8Array, nonce: Uint8Array, key: Uint8Array) : dynamic + @JsName("crypto_secretbox_open_easy") + fun crypto_secretbox_open_easy(ciphertext : Uint8Array, nonce: Uint8Array, key: Uint8Array) : dynamic + + + // ---- SecretBox End ---- + + + // ---- AEAD ---- + @JsName("crypto_aead_chacha20poly1305_decrypt") + fun crypto_aead_chacha20poly1305_decrypt(nsec : Uint8Array?, ciphertext: Uint8Array, associatedData: Uint8Array, npub: Uint8Array, key: Uint8Array) : Uint8Array + @JsName("crypto_aead_chacha20poly1305_decrypt_detached") + fun crypto_aead_chacha20poly1305_decrypt_detached(nsec: Uint8Array?, ciphertext: Uint8Array, mac: Uint8Array, associatedData: Uint8Array, npub: Uint8Array, key: Uint8Array): Uint8Array + @JsName("crypto_aead_chacha20poly1305_encrypt") + fun crypto_aead_chacha20poly1305_encrypt(message: Uint8Array, associatedData: Uint8Array, nsec: Uint8Array?, npub: Uint8Array, key: Uint8Array) : Uint8Array + @JsName("crypto_aead_chacha20poly1305_encrypt_detached") + fun crypto_aead_chacha20poly1305_encrypt_detached(message: Uint8Array, associatedData: Uint8Array, nsec: Uint8Array?, npub: Uint8Array, key: Uint8Array) : dynamic + @JsName("crypto_aead_chacha20poly1305_ietf_decrypt") + fun crypto_aead_chacha20poly1305_ietf_decrypt(nsec : Uint8Array?, ciphertext: Uint8Array, associatedData: Uint8Array, npub: Uint8Array, key: Uint8Array) : Uint8Array + @JsName("crypto_aead_chacha20poly1305_ietf_decrypt_detached") + fun crypto_aead_chacha20poly1305_ietf_decrypt_detached(nsec: Uint8Array?, ciphertext: Uint8Array, mac: Uint8Array, associatedData: Uint8Array, npub: Uint8Array, key: Uint8Array): Uint8Array + @JsName("crypto_aead_chacha20poly1305_ietf_encrypt") + fun crypto_aead_chacha20poly1305_ietf_encrypt(message: Uint8Array, associatedData: Uint8Array, nsec: Uint8Array?, npub: Uint8Array, key: Uint8Array) : Uint8Array + @JsName("crypto_aead_chacha20poly1305_ietf_encrypt_detached") + fun crypto_aead_chacha20poly1305_ietf_encrypt_detached(message: Uint8Array, associatedData: Uint8Array, nsec: Uint8Array?, npub: Uint8Array, key: Uint8Array) : dynamic + @JsName("crypto_aead_chacha20poly1305_ietf_keygen") + fun crypto_aead_chacha20poly1305_ietf_keygen() : Uint8Array + @JsName("crypto_aead_chacha20poly1305_keygen") + fun crypto_aead_chacha20poly1305_keygen() : Uint8Array + @JsName("crypto_aead_xchacha20poly1305_ietf_decrypt") + fun crypto_aead_xchacha20poly1305_ietf_decrypt(nsec : Uint8Array?, ciphertext: Uint8Array, associatedData: Uint8Array, npub: Uint8Array, key: Uint8Array) : Uint8Array + @JsName("crypto_aead_xchacha20poly1305_ietf_decrypt_detached") + fun crypto_aead_xchacha20poly1305_ietf_decrypt_detached(nsec: Uint8Array?, ciphertext: Uint8Array, mac: Uint8Array, associatedData: Uint8Array, npub: Uint8Array, key: Uint8Array): Uint8Array + @JsName("crypto_aead_xchacha20poly1305_ietf_encrypt") + fun crypto_aead_xchacha20poly1305_ietf_encrypt(message: Uint8Array, associatedData: Uint8Array, nsec: Uint8Array?, npub: Uint8Array, key: Uint8Array) : Uint8Array + @JsName("crypto_aead_xchacha20poly1305_ietf_encrypt_detached") + fun crypto_aead_xchacha20poly1305_ietf_encrypt_detached(message: Uint8Array, associatedData: Uint8Array, nsec: Uint8Array?, npub: Uint8Array, key: Uint8Array) : dynamic + @JsName("crypto_aead_xchacha20poly1305_ietf_keygen") + fun crypto_aead_xchacha20poly1305_ietf_keygen(): Uint8Array + + // ---- AEAD end ---- + + // ---- Auth ---- + + @JsName("crypto_auth") + fun crypto_auth(message: Uint8Array, key: Uint8Array) : Uint8Array + @JsName("crypto_auth_keygen") + fun crypto_auth_keygen() : Uint8Array + @JsName("crypto_auth_verify") + fun crypto_auth_verify(tag: Uint8Array, message: Uint8Array, key: Uint8Array) : Boolean + @JsName("crypto_auth_hmacsha256") + fun crypto_auth_hmacsha256(message: Uint8Array, key: Uint8Array) : Uint8Array + @JsName("crypto_auth_hmacsha256_keygen") + fun crypto_auth_hmacsha256_keygen() : Uint8Array + @JsName("crypto_auth_hmacsha256_verify") + fun crypto_auth_hmacsha256_verify(tag: Uint8Array, message: Uint8Array, key: Uint8Array) : Boolean + @JsName("crypto_auth_hmacsha512") + fun crypto_auth_hmacsha512(message: Uint8Array, key: Uint8Array) : Uint8Array + @JsName("crypto_auth_hmacsha512_keygen") + fun crypto_auth_hmacsha512_keygen() : Uint8Array + @JsName("crypto_auth_hmacsha512_verify") + fun crypto_auth_hmacsha512_verify(tag: Uint8Array, message: Uint8Array, key: Uint8Array) : Boolean + + // ---- Auth end ---- + + // ---- Box ---- + + @JsName("crypto_box_keypair") + fun crypto_box_keypair() : dynamic + @JsName("crypto_box_seed_keypair") + fun crypto_box_seed_keypair(seed : Uint8Array) : dynamic + @JsName("crypto_box_easy") + fun crypto_box_easy(message: Uint8Array, + nonce: Uint8Array, + recipientsPublicKey: Uint8Array, + sendersSecretKey: Uint8Array) : Uint8Array + @JsName("crypto_box_open_easy") + fun crypto_box_open_easy(ciphertext: Uint8Array, + nonce: Uint8Array, + sendersPublicKey: Uint8Array, + recipientsSecretKey: Uint8Array) : Uint8Array + @JsName("crypto_box_detached") + fun crypto_box_detached(message: Uint8Array, + nonce: Uint8Array, + recipientsPublicKey: Uint8Array, + sendersSecretKey: Uint8Array) : dynamic + @JsName("crypto_box_open_detached") + fun crypto_box_open_detached(ciphertext: Uint8Array, + tag: Uint8Array, + nonce: Uint8Array, + sendersPublicKey: Uint8Array, + recipientsSecretKey: Uint8Array) : Uint8Array + @JsName("crypto_box_beforenm") + fun crypto_box_beforenm(publicKey: Uint8Array, secretKey: Uint8Array) : Uint8Array + @JsName("crypto_box_easy_afternm") + fun crypto_box_easy_afternm(message: Uint8Array, + nonce: Uint8Array, + precomputedKey: Uint8Array) : Uint8Array + @JsName("crypto_box_open_easy_afternm") + fun crypto_box_open_easy_afternm(ciphertext: Uint8Array, + nonce: Uint8Array, + precomputedKey: Uint8Array) : Uint8Array + @JsName("crypto_box_seal") + fun crypto_box_seal(message: Uint8Array, recipientsPublicKey: Uint8Array) : Uint8Array + @JsName("crypto_box_seal_open") + fun crypto_box_seal_open(ciphertext: Uint8Array, recipientsPublicKey: Uint8Array, recipientsSecretKey: Uint8Array) : Uint8Array + + // ---- Box end ---- + + // ---- Sign start ---- + @JsName("crypto_sign") + fun crypto_sign(message: Uint8Array, secretKey: Uint8Array) : Uint8Array + @JsName("crypto_sign_detached") + fun crypto_sign_detached(message: Uint8Array, secretKey: Uint8Array) : Uint8Array + @JsName("crypto_sign_ed25519_pk_to_curve25519") + fun crypto_sign_ed25519_pk_to_curve25519(ed25519PublicKey: Uint8Array) : Uint8Array + @JsName("crypto_sign_ed25519_sk_to_curve25519") + fun crypto_sign_ed25519_sk_to_curve25519(ed25519SecretKey: Uint8Array) : Uint8Array + @JsName("crypto_sign_ed25519_sk_to_pk") + fun crypto_sign_ed25519_sk_to_pk(ed25519SecretKey: Uint8Array) : Uint8Array + @JsName("crypto_sign_ed25519_sk_to_seed") + fun crypto_sign_ed25519_sk_to_seed(ed25519SecretKey: Uint8Array) : Uint8Array + @JsName("crypto_sign_final_create") + fun crypto_sign_final_create(state: dynamic, secretKey: Uint8Array) : Uint8Array + @JsName("crypto_sign_final_verify") + fun crypto_sign_final_verify(state: dynamic, signature: Uint8Array, publicKey: Uint8Array) : Boolean + @JsName("crypto_sign_init") + fun crypto_sign_init() : dynamic + @JsName("crypto_sign_keypair") + fun crypto_sign_keypair() : dynamic + @JsName("crypto_sign_open") + fun crypto_sign_open(signedMessage: Uint8Array, publicKey: Uint8Array) : Uint8Array + @JsName("crypto_sign_seed_keypair") + fun crypto_sign_seed_keypair(seed: Uint8Array) : dynamic + @JsName("crypto_sign_update") + fun crypto_sign_update(state: dynamic, message: Uint8Array) + @JsName("crypto_sign_verify_detached") + fun crypto_sign_verify_detached(signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array) : Boolean + + + // ---- Sign end ---- + + + // ---- KDF ---- + + @JsName("crypto_kdf_derive_from_key") + fun crypto_kdf_derive_from_key(subkey_len: UInt, subkeyId : UInt, ctx: String, key: Uint8Array) : Uint8Array + @JsName("crypto_kdf_keygen") + fun crypto_kdf_keygen() : Uint8Array + + // ---- KDF end ----- + + // ---- Password hashing ---- + + @JsName("crypto_pwhash") + fun crypto_pwhash(keyLength : UInt, password : Uint8Array, salt: Uint8Array, opsLimit: UInt, memLimit: UInt, algorithm: UInt) : Uint8Array + @JsName("crypto_pwhash_str") + fun crypto_pwhash_str(password: Uint8Array, opsLimit: UInt, memLimit: UInt) : String + @JsName("crypto_pwhash_str_needs_rehash") + fun crypto_pwhash_str_needs_rehash(hashedPassword: String, opsLimit: UInt, memLimit: UInt) : Boolean + @JsName("crypto_pwhash_str_verify") + fun crypto_pwhash_str_verify(hashedPassword: String, password: Uint8Array) : Boolean + + + // ---- Password hashing end ---- + + // ---- Utils ---- + + @JsName("memcmp") + fun memcmp(first: Uint8Array, second: Uint8Array) : Boolean + @JsName("memzero") + fun memzero(data: Uint8Array) + @JsName("pad") + fun pad(data : Uint8Array, blocksize: Int) : Uint8Array + @JsName("unpad") + fun unpad(data: Uint8Array, blocksize: Int) : Uint8Array + @JsName("to_base64") + fun to_base64(data: Uint8Array, variant: Int) : String + @JsName("to_hex") + fun to_hex(data: Uint8Array) : String + @JsName("to_string") + fun to_string(data: Uint8Array) : String + @JsName("from_base64") + fun from_base64(data: String, variant: Int): Uint8Array + @JsName("from_hex") + fun from_hex(data : String): Uint8Array + @JsName("from_string") + fun from_string(data : String): Uint8Array + + // ---- > ---- Random ---- < ----- + + @JsName("randombytes_buf") + fun randombytes_buf(length: Int) : Uint8Array + @JsName("randombytes_buf_deterministic") + fun randombytes_buf_deterministic(length: UInt, seed : Uint8Array) : Uint8Array + @JsName("randombytes_random") + fun randombytes_random() : UInt + @JsName("randombytes_uniform") + fun randombytes_uniform(upper_bound: UInt) : UInt + + // ---- Utils end ---- + + // ---- Key exchange ---- + @JsName("crypto_kx_client_session_keys") + fun crypto_kx_client_session_keys(clientPublicKey: Uint8Array, clientSecretKey: Uint8Array, serverPublicKey: Uint8Array) : dynamic + @JsName("crypto_kx_keypair") + fun crypto_kx_keypair() : dynamic + @JsName("crypto_kx_seed_keypair") + fun crypto_kx_seed_keypair(seed: Uint8Array) : dynamic + @JsName("crypto_kx_server_session_keys") + fun crypto_kx_server_session_keys(serverPublicKey: Uint8Array, serverSecretKey: Uint8Array, clientPublicKey: Uint8Array) : dynamic + + // ---- Key exchange end ---- + + // -- Stream ---- + @JsName("crypto_stream_chacha20") + fun crypto_stream_chacha20(outLength: UInt, key: Uint8Array, nonce: Uint8Array) : Uint8Array + @JsName("crypto_stream_chacha20_ietf_xor") + fun crypto_stream_chacha20_ietf_xor(message : Uint8Array, nonce: Uint8Array, key: Uint8Array) : Uint8Array + @JsName("crypto_stream_chacha20_ietf_xor_ic") + fun crypto_stream_chacha20_ietf_xor_ic(message : Uint8Array, nonce: Uint8Array, initialCounter: UInt, key: Uint8Array) : Uint8Array + @JsName("crypto_stream_chacha20_keygen") + fun crypto_stream_chacha20_keygen() : Uint8Array + @JsName("crypto_stream_chacha20_xor") + fun crypto_stream_chacha20_xor(message : Uint8Array, nonce: Uint8Array, key: Uint8Array) : Uint8Array + @JsName("crypto_stream_chacha20_xor_ic") + fun crypto_stream_chacha20_xor_ic(message : Uint8Array, nonce: Uint8Array, initialCounter: UInt, key: Uint8Array) : Uint8Array + + @JsName("crypto_stream_xchacha20_keygen") + fun crypto_stream_xchacha20_keygen() : Uint8Array + @JsName("crypto_stream_xchacha20_xor") + fun crypto_stream_xchacha20_xor(message : Uint8Array, nonce: Uint8Array, key: Uint8Array) : Uint8Array + @JsName("crypto_stream_xchacha20_xor_ic") + fun crypto_stream_xchacha20_xor_ic(message : Uint8Array, nonce: Uint8Array, initialCounter: UInt, key: Uint8Array) : Uint8Array + + // ---- Stream end ---- + + // ---- Scalar multiplication ---- + + @JsName("crypto_scalarmult") + fun crypto_scalarmult(privateKey: Uint8Array, publicKey: Uint8Array) : Uint8Array + @JsName("crypto_scalarmult_base") + fun crypto_scalarmult_base(privateKey: Uint8Array) : Uint8Array + + // ---- Scalar multiplication end ---- + + + +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/JsSodiumLoader.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/JsSodiumLoader.kt new file mode 100644 index 0000000..a4509a0 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/JsSodiumLoader.kt @@ -0,0 +1,55 @@ +package ext.libsodium.com.ionspin.kotlin.crypto + +import com.ionspin.kotlin.crypto.getSodiumLoaded +import com.ionspin.kotlin.crypto.sodiumLoaded +import ext.libsodium._libsodiumPromise +import ext.libsodium.crypto_generichash +import ext.libsodium.crypto_hash_sha256 +import ext.libsodium.crypto_hash_sha256_init +import ext.libsodium.crypto_hash_sha512 +import ext.libsodium.sodium_init +import kotlin.coroutines.suspendCoroutine + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 27-May-2020 + */ +object JsSodiumLoader { + + class _EmitJsSodiumFunction { + init { + println(::crypto_generichash) + println(::crypto_hash_sha256) + println(::crypto_hash_sha512) + println(::crypto_hash_sha256_init) + } + + } + + suspend fun load() = suspendCoroutine { continuation -> + if (!getSodiumLoaded()) { + _libsodiumPromise.then { + sodium_init() + sodiumLoaded = true + continuation.resumeWith(Result.success(Unit)) + }.catch { e -> + continuation.resumeWith(Result.failure(e)) + } + } else { + continuation.resumeWith(Result.success(Unit)) + } + } + + fun loadWithCallback(doneCallback: () -> (Unit)) { + if (!getSodiumLoaded()) { + _libsodiumPromise.then { + sodium_init() + sodiumLoaded = true + doneCallback.invoke() + } + } else { + doneCallback.invoke() + } + } +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/JsUtil.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/JsUtil.kt new file mode 100644 index 0000000..ec40078 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/JsUtil.kt @@ -0,0 +1,27 @@ +package ext.libsodium.com.ionspin.kotlin.crypto + +import org.khronos.webgl.Uint8Array +import org.khronos.webgl.get + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 02-Aug-2020 + */ +fun UByteArray.toUInt8Array() : Uint8Array { + val uint8Result = Uint8Array(toByteArray().toTypedArray()) + return uint8Result +} + + +fun Uint8Array.toUByteArray() : UByteArray { + if (length.asDynamic() == undefined) { + println("Error") + } + val result = UByteArray(length) + for (i in 0 until length) { + result[i] = get(i).toUByte() + } + + return result +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/LibsodiumInitializer.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/LibsodiumInitializer.kt new file mode 100644 index 0000000..20bf538 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/LibsodiumInitializer.kt @@ -0,0 +1,36 @@ +package com.ionspin.kotlin.crypto + +import ext.libsodium.com.ionspin.kotlin.crypto.JsSodiumInterface +import ext.libsodium.com.ionspin.kotlin.crypto.JsSodiumLoader + +var sodiumLoaded: Boolean = false + +fun getSodium() : JsSodiumInterface = JsSodiumInterface + +fun getSodiumLoaded() : Boolean = sodiumLoaded + +fun setSodiumLoaded(loaded: Boolean) { + sodiumLoaded = loaded +} + +actual object LibsodiumInitializer { + private var isPlatformInitialized = false + + actual suspend fun initialize() { + JsSodiumLoader.load() + isPlatformInitialized = true + } + + actual fun initializeWithCallback(done: () -> Unit) { + JsSodiumLoader.loadWithCallback { + isPlatformInitialized = true + done() + } + } + + actual fun isInitialized(): Boolean { + return isPlatformInitialized + } + + +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/aead/AuthenticatedEncryptionWithAssociatedData.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/aead/AuthenticatedEncryptionWithAssociatedData.kt new file mode 100644 index 0000000..c74369b --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/aead/AuthenticatedEncryptionWithAssociatedData.kt @@ -0,0 +1,247 @@ +package com.ionspin.kotlin.crypto.aead + +import com.ionspin.kotlin.crypto.getSodium +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array +import org.khronos.webgl.Uint8Array + +actual object AuthenticatedEncryptionWithAssociatedData { + + // Ietf + + // Original chacha20poly1305 + actual fun xChaCha20Poly1305IetfEncrypt( + message: UByteArray, + associatedData: UByteArray, + nonce: UByteArray, + key: UByteArray + ): UByteArray { + return getSodium().crypto_aead_xchacha20poly1305_ietf_encrypt( + message.toUInt8Array(), + associatedData.toUInt8Array(), + null, + nonce.toUInt8Array(), + key.toUInt8Array(), + ).toUByteArray() + } + + actual fun xChaCha20Poly1305IetfDecrypt( + ciphertextAndTag: UByteArray, + associatedData: UByteArray, + nonce: UByteArray, + key: UByteArray + ): UByteArray { + try { + return getSodium().crypto_aead_xchacha20poly1305_ietf_decrypt( + null, + ciphertextAndTag.toUInt8Array(), + associatedData.toUInt8Array(), + nonce.toUInt8Array(), + key.toUInt8Array() + ).toUByteArray() + } catch (error: Throwable) { + throw AeadCorrupedOrTamperedDataException() + } + } + + actual fun xChaCha20Poly1305IetfEncryptDetached( + message: UByteArray, + associatedData: UByteArray, + nonce: UByteArray, + key: UByteArray + ): AeadEncryptedDataAndTag { + val result = getSodium().crypto_aead_xchacha20poly1305_ietf_encrypt_detached( + message.toUInt8Array(), + associatedData.toUInt8Array(), + null, + nonce.toUInt8Array(), + key.toUInt8Array(), + ) + return AeadEncryptedDataAndTag( + (result.ciphertext as Uint8Array).toUByteArray(), + (result.mac as Uint8Array).toUByteArray() + ) + } + + actual fun xChaCha20Poly1305IetfDecryptDetached( + ciphertext: UByteArray, + tag: UByteArray, + associatedData: UByteArray, + nonce: UByteArray, + key: UByteArray + ): UByteArray { + try { + return getSodium().crypto_aead_xchacha20poly1305_ietf_decrypt_detached( + null, + ciphertext.toUInt8Array(), + tag.toUInt8Array(), + associatedData.toUInt8Array(), + nonce.toUInt8Array(), + key.toUInt8Array() + ).toUByteArray() + } catch (error: Throwable) { + throw AeadCorrupedOrTamperedDataException() + } + } + + actual fun chaCha20Poly1305IetfEncrypt( + message: UByteArray, + associatedData: UByteArray, + nonce: UByteArray, + key: UByteArray + ): UByteArray { + return getSodium().crypto_aead_chacha20poly1305_ietf_encrypt( + message.toUInt8Array(), + associatedData.toUInt8Array(), + null, + nonce.toUInt8Array(), + key.toUInt8Array(), + ).toUByteArray() + } + + actual fun chaCha20Poly1305IetfDecrypt( + ciphertextAndTag: UByteArray, + associatedData: UByteArray, + nonce: UByteArray, + key: UByteArray + ): UByteArray { + try { + return getSodium().crypto_aead_chacha20poly1305_ietf_decrypt( + null, + ciphertextAndTag.toUInt8Array(), + associatedData.toUInt8Array(), + nonce.toUInt8Array(), + key.toUInt8Array() + ).toUByteArray() + } catch (error: Throwable) { + throw AeadCorrupedOrTamperedDataException() + } + } + + actual fun chaCha20Poly1305IetfEncryptDetached( + message: UByteArray, + associatedData: UByteArray, + nonce: UByteArray, + key: UByteArray + ): AeadEncryptedDataAndTag { + val result = getSodium().crypto_aead_chacha20poly1305_ietf_encrypt_detached( + message.toUInt8Array(), + associatedData.toUInt8Array(), + null, + nonce.toUInt8Array(), + key.toUInt8Array(), + ) + return AeadEncryptedDataAndTag( + (result.ciphertext as Uint8Array).toUByteArray(), + (result.mac as Uint8Array).toUByteArray() + ) + } + + actual fun chaCha20Poly1305IetfDecryptDetached( + ciphertext: UByteArray, + tag: UByteArray, + associatedData: UByteArray, + nonce: UByteArray, + key: UByteArray + ): UByteArray { + try { + return getSodium().crypto_aead_chacha20poly1305_ietf_decrypt_detached( + null, + ciphertext.toUInt8Array(), + tag.toUInt8Array(), + associatedData.toUInt8Array(), + nonce.toUInt8Array(), + key.toUInt8Array() + ).toUByteArray() + } catch (error: Throwable) { + throw AeadCorrupedOrTamperedDataException() + } + } + + actual fun chaCha20Poly1305Encrypt( + message: UByteArray, + associatedData: UByteArray, + nonce: UByteArray, + key: UByteArray + ): UByteArray { + return getSodium().crypto_aead_chacha20poly1305_encrypt( + message.toUInt8Array(), + associatedData.toUInt8Array(), + null, + nonce.toUInt8Array(), + key.toUInt8Array(), + ).toUByteArray() + } + + actual fun chaCha20Poly1305Decrypt( + ciphertextAndTag: UByteArray, + associatedData: UByteArray, + nonce: UByteArray, + key: UByteArray + ): UByteArray { + try { + return getSodium().crypto_aead_chacha20poly1305_decrypt( + null, + ciphertextAndTag.toUInt8Array(), + associatedData.toUInt8Array(), + nonce.toUInt8Array(), + key.toUInt8Array() + ).toUByteArray() + } catch (error: Throwable) { + throw AeadCorrupedOrTamperedDataException() + } + } + + actual fun chaCha20Poly1305EncryptDetached( + message: UByteArray, + associatedData: UByteArray, + nonce: UByteArray, + key: UByteArray + ): AeadEncryptedDataAndTag { + val result = getSodium().crypto_aead_chacha20poly1305_encrypt_detached( + message.toUInt8Array(), + associatedData.toUInt8Array(), + null, + nonce.toUInt8Array(), + key.toUInt8Array(), + ) + return AeadEncryptedDataAndTag( + (result.ciphertext as Uint8Array).toUByteArray(), + (result.mac as Uint8Array).toUByteArray() + ) + } + + actual fun chaCha20Poly1305DecryptDetached( + ciphertext: UByteArray, + tag: UByteArray, + associatedData: UByteArray, + nonce: UByteArray, + key: UByteArray + ): UByteArray { + try { + return getSodium().crypto_aead_chacha20poly1305_decrypt_detached( + null, + ciphertext.toUInt8Array(), + tag.toUInt8Array(), + associatedData.toUInt8Array(), + nonce.toUInt8Array(), + key.toUInt8Array() + ).toUByteArray() + } catch (error: Throwable) { + throw AeadCorrupedOrTamperedDataException() + } + } + + actual fun xChaCha20Poly1305IetfKeygen(): UByteArray { + return getSodium().crypto_aead_xchacha20poly1305_ietf_keygen().toUByteArray() + } + + actual fun chaCha20Poly1305IetfKeygen(): UByteArray { + return getSodium().crypto_aead_chacha20poly1305_ietf_keygen().toUByteArray() + } + + actual fun chaCha20Poly1305Keygen(): UByteArray { + return getSodium().crypto_aead_chacha20poly1305_keygen().toUByteArray() + } + +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/auth/Auth.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/auth/Auth.kt new file mode 100644 index 0000000..338cc3d --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/auth/Auth.kt @@ -0,0 +1,74 @@ +package com.ionspin.kotlin.crypto.auth + +import com.ionspin.kotlin.crypto.getSodium +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array + +actual object Auth { + actual fun authKeygen(): UByteArray { + return getSodium().crypto_auth_keygen().toUByteArray() + } + + actual fun auth(message: UByteArray, key: UByteArray): UByteArray { + return getSodium().crypto_auth( + message.toUInt8Array(), + key.toUInt8Array() + ).toUByteArray() + + } + + actual fun authVerify(tag: UByteArray, message: UByteArray, key: UByteArray): Boolean { + return getSodium().crypto_auth_verify( + tag.toUInt8Array(), + message.toUInt8Array(), + key.toUInt8Array() + ) + } + + actual fun authHmacSha256Keygen(): UByteArray { + return getSodium().crypto_auth_hmacsha256_keygen().toUByteArray() + } + + actual fun authHmacSha256(message: UByteArray, key: UByteArray): UByteArray { + return getSodium().crypto_auth_hmacsha256( + message.toUInt8Array(), + key.toUInt8Array() + ).toUByteArray() + } + + actual fun authHmacSha256Verify( + tag: UByteArray, + message: UByteArray, + key: UByteArray + ): Boolean { + return getSodium().crypto_auth_hmacsha256_verify( + tag.toUInt8Array(), + message.toUInt8Array(), + key.toUInt8Array() + ) + } + + actual fun authHmacSha512Keygen(): UByteArray { + return getSodium().crypto_auth_hmacsha512_keygen().toUByteArray() + } + + actual fun authHmacSha512(message: UByteArray, key: UByteArray): UByteArray { + return getSodium().crypto_auth_hmacsha512( + message.toUInt8Array(), + key.toUInt8Array() + ).toUByteArray() + } + + actual fun authHmacSha512Verify( + tag: UByteArray, + message: UByteArray, + key: UByteArray + ): Boolean { + return getSodium().crypto_auth_hmacsha512_verify( + tag.toUInt8Array(), + message.toUInt8Array(), + key.toUInt8Array() + ) + } + +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/box/Box.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/box/Box.kt new file mode 100644 index 0000000..376f4a6 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/box/Box.kt @@ -0,0 +1,200 @@ +package com.ionspin.kotlin.crypto.box + +import com.ionspin.kotlin.crypto.getSodium +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array +import org.khronos.webgl.Uint8Array + +actual object Box { + /** + * The crypto_box_keypair() function randomly generates a secret key and a corresponding public key. + * The public key is put into pk (crypto_box_PUBLICKEYBYTES bytes) and the secret key into + * sk (crypto_box_SECRETKEYBYTES bytes). + */ + actual fun keypair(): BoxKeyPair { + val keypair = getSodium().crypto_box_keypair() + return BoxKeyPair( + (keypair.publicKey as Uint8Array).toUByteArray(), + (keypair.privateKey as Uint8Array).toUByteArray() + ) + } + + /** + * Using crypto_box_seed_keypair(), the key pair can also be deterministically derived from a single key seed (crypto_box_SEEDBYTES bytes). + */ + actual fun seedKeypair(seed: UByteArray): BoxKeyPair { + val keypair = getSodium().crypto_box_seed_keypair(seed.toUInt8Array()) + return BoxKeyPair( + (keypair.publicKey as Uint8Array).toUByteArray(), + (keypair.privateKey as Uint8Array).toUByteArray() + ) + } + + /** + * The crypto_box_easy() function encrypts a message m whose length is mlen bytes, with a recipient's public key pk, a sender's secret key sk and a nonce n. + * n should be crypto_box_NONCEBYTES bytes. + * c should be at least crypto_box_MACBYTES + mlen bytes long. + * This function writes the authentication tag, whose length is crypto_box_MACBYTES bytes, in c, + * immediately followed by the encrypted message, whose length is the same as the plaintext: mlen. + */ + actual fun easy( + message: UByteArray, + nonce: UByteArray, + recipientsPublicKey: UByteArray, + sendersSecretKey: UByteArray + ): UByteArray { + return getSodium().crypto_box_easy( + message.toUInt8Array(), + nonce.toUInt8Array(), + recipientsPublicKey.toUInt8Array(), + sendersSecretKey.toUInt8Array(), + ).toUByteArray() + + } + + /** + * The crypto_box_open_easy() function verifies and decrypts a ciphertext produced by crypto_box_easy(). + * c is a pointer to an authentication tag + encrypted message combination, as produced by crypto_box_easy(). clen is the length of this authentication tag + encrypted message combination. Put differently, clen is the number of bytes written by crypto_box_easy(), which is crypto_box_MACBYTES + the length of the message. + * The nonce n has to match the nonce used to encrypt and authenticate the message. + * pk is the public key of the sender that encrypted the message. sk is the secret key of the recipient that is willing to verify and decrypt it. + * The function throws [BoxCorruptedOrTamperedDataException] if the verification fails. + */ + actual fun openEasy( + ciphertext: UByteArray, + nonce: UByteArray, + sendersPublicKey: UByteArray, + recipientsSecretKey: UByteArray + ): UByteArray { + try { + return getSodium().crypto_box_open_easy( + ciphertext.toUInt8Array(), + nonce.toUInt8Array(), + sendersPublicKey.toUInt8Array(), + recipientsSecretKey.toUInt8Array(), + ).toUByteArray() + } catch (error: Throwable) { + throw BoxCorruptedOrTamperedDataException() + } + + } + + /** + * The crypto_box_beforenm() function computes a shared secret key given a public key pk and a secret key sk, + * and puts it into k (crypto_box_BEFORENMBYTES bytes). + */ + actual fun beforeNM(publicKey: UByteArray, secretKey: UByteArray): UByteArray { + return getSodium().crypto_box_beforenm( + publicKey.toUInt8Array(), + secretKey.toUInt8Array() + ).toUByteArray() + } + + /** + * The _afternm variants of the previously described functions accept a precalculated shared secret key k instead of a key pair. + */ + actual fun easyAfterNM( + message: UByteArray, + nonce: UByteArray, + precomputedKey: UByteArray + ): UByteArray { + return getSodium().crypto_box_easy_afternm( + message.toUInt8Array(), + nonce.toUInt8Array(), + precomputedKey.toUInt8Array() + ).toUByteArray() + } + + /** + * The _afternm variants of the previously described functions accept a precalculated shared secret key k instead of a key pair. + */ + actual fun openEasyAfterNM( + ciphertext: UByteArray, + nonce: UByteArray, + precomputedKey: UByteArray + ): UByteArray { + try { + return getSodium().crypto_box_open_easy_afternm( + ciphertext.toUInt8Array(), + nonce.toUInt8Array(), + precomputedKey.toUInt8Array(), + ).toUByteArray() + } catch (error: Throwable) { + throw BoxCorruptedOrTamperedDataException() + } + } + + /** + * This function encrypts a message m of length mlen with a nonce n and a secret key sk for a recipient whose + * public key is pk, and puts the encrypted message into c. + * Exactly mlen bytes will be put into c, since this function does not prepend the authentication tag. + * The tag, whose size is crypto_box_MACBYTES bytes, will be put into mac. + */ + actual fun detached( + message: UByteArray, + nonce: UByteArray, + recipientsPublicKey: UByteArray, + sendersSecretKey: UByteArray + ): BoxEncryptedDataAndTag { + val detached = getSodium().crypto_box_detached( + message.toUInt8Array(), + nonce.toUInt8Array(), + recipientsPublicKey.toUInt8Array(), + sendersSecretKey.toUInt8Array(), + ) + return BoxEncryptedDataAndTag( + (detached.ciphertext as Uint8Array).toUByteArray(), + (detached.mac as Uint8Array).toUByteArray() + ) + } + + /** + * The crypto_box_open_detached() function verifies and decrypts an encrypted message c whose length is clen using the recipient's secret key sk and the sender's public key pk. + * clen doesn't include the tag, so this length is the same as the plaintext. + * The plaintext is put into m after verifying that mac is a valid authentication tag for this ciphertext, with the given nonce n and key k. + * The function throws [BoxCorruptedOrTamperedDataException] if the verification fails. + */ + actual fun openDetached( + ciphertext: UByteArray, + tag: UByteArray, + nonce: UByteArray, + sendersPublicKey: UByteArray, + recipientsSecretKey: UByteArray + ): UByteArray { + try { + return getSodium().crypto_box_open_detached( + ciphertext.toUInt8Array(), + tag.toUInt8Array(), + nonce.toUInt8Array(), + sendersPublicKey.toUInt8Array(), + recipientsSecretKey.toUInt8Array(), + ).toUByteArray() + } catch (error: Throwable) { + throw BoxCorruptedOrTamperedDataException() + } + } + + actual fun seal(message: UByteArray, recipientsPublicKey: UByteArray): UByteArray { + return getSodium().crypto_box_seal( + message.toUInt8Array(), + recipientsPublicKey.toUInt8Array() + ).toUByteArray() + } + + actual fun sealOpen( + ciphertext: UByteArray, + recipientsPublicKey: UByteArray, + recipientsSecretKey: UByteArray + ): UByteArray { + try { + return getSodium().crypto_box_seal_open( + ciphertext.toUInt8Array(), + recipientsPublicKey.toUInt8Array(), + recipientsSecretKey.toUInt8Array(), + ).toUByteArray() + } catch (error: Throwable) { + throw BoxCorruptedOrTamperedDataException() + } + } + + +} \ No newline at end of file diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/generichash/GenericHash.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/generichash/GenericHash.kt new file mode 100644 index 0000000..3ea5001 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/generichash/GenericHash.kt @@ -0,0 +1,82 @@ +package com.ionspin.kotlin.crypto.generichash + +import com.ionspin.kotlin.crypto.getSodium +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array +import org.khronos.webgl.Uint8Array + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 21-Aug-2020 + */ + +actual typealias GenericHashStateInternal = Any + +actual object GenericHash { + actual fun genericHash( + message: UByteArray, + requestedHashLength: Int, + key: UByteArray? + ): UByteArray { + return getSodium().crypto_generichash( + requestedHashLength, + message.toUInt8Array(), + key?.toUInt8Array() ?: Uint8Array(0) + ).toUByteArray() + } + + actual fun genericHashInit( + requestedHashLength: Int, + key: UByteArray? + ): GenericHashState { + val state = getSodium().crypto_generichash_init(key?.toUInt8Array() ?: Uint8Array(0), requestedHashLength) + return GenericHashState(requestedHashLength, state) + } + + actual fun genericHashUpdate( + state: GenericHashState, + messagePart: UByteArray + ) { + getSodium().crypto_generichash_update(state.internalState, messagePart.toUInt8Array()) + } + + actual fun genericHashFinal(state: GenericHashState): UByteArray { + return getSodium().crypto_generichash_final(state.internalState, state.hashLength).toUByteArray() + } + + actual fun genericHashKeygen(): UByteArray { + return getSodium().crypto_generichash_keygen().toUByteArray() + } +// -- Not present in LazySodium nor libsodium.js +// actual fun blake2b(message: UByteArray, requestedHashLength: Int, key: UByteArray?) : UByteArray { +// return getSodium().crypto_generichash_blake2b( +// requestedHashLength, +// message.toUInt8Array(), +// key?.toUInt8Array() ?: Uint8Array(0) +// ).toUByteArray() +// } +// +// actual fun blake2bInit( +// requestedHashLength: Int, +// key: UByteArray? +// ): Blake2bState { +// val state = getSodium().crypto_generichash_blake2b_init(key?.toUInt8Array() ?: Uint8Array(0), requestedHashLength) +// return Blake2bState(requestedHashLength, state) +// } +// +// actual fun blake2bUpdate( +// state: GenericHashState, +// messagePart: UByteArray +// ) { +// getSodium().crypto_generichash_blake2b_update(state.internalState, messagePart.toUInt8Array()) +// } +// +// actual fun blake2bFinal(state: GenericHashState): UByteArray { +// return getSodium().crypto_generichash_blake2b_final(state.internalState, state.hashLength).toUByteArray() +// } +// +// actual fun blake2bKeygen(): UByteArray { +// return getSodium().crypto_generichash_blake2b_keygen().toUByteArray() +// } +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/hash/Hash.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/hash/Hash.kt new file mode 100644 index 0000000..5956ba8 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/hash/Hash.kt @@ -0,0 +1,45 @@ +package com.ionspin.kotlin.crypto.hash + +import com.ionspin.kotlin.crypto.getSodium +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array + +actual typealias Sha256State = Any +actual typealias Sha512State = Any + +actual object Hash { + + actual fun sha256(data: UByteArray): UByteArray { + return getSodium().crypto_hash_sha256(data.toUInt8Array()).toUByteArray() + } + + actual fun sha256Init(): Sha256State { + return getSodium().crypto_hash_sha256_init() + } + + actual fun sha256Update(state: Sha256State, data: UByteArray) { + getSodium().crypto_hash_sha256_update(state, data.toUInt8Array()) + } + + actual fun sha256Final(state: Sha256State): UByteArray { + return getSodium().crypto_hash_sha256_final(state).toUByteArray() + } + + actual fun sha512(data: UByteArray): UByteArray { + return getSodium().crypto_hash_sha512(data.toUInt8Array()).toUByteArray() + } + + actual fun sha512Init(): Sha512State { + return getSodium().crypto_hash_sha512_init() + } + + actual fun sha512Update(state: Sha512State, data: UByteArray) { + getSodium().crypto_hash_sha512_update(state, data.toUInt8Array()) + } + + actual fun sha512Final(state: Sha512State): UByteArray { + return getSodium().crypto_hash_sha512_final(state).toUByteArray() + } + + +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/kdf/Kdf.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/kdf/Kdf.kt new file mode 100644 index 0000000..8cf0a31 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/kdf/Kdf.kt @@ -0,0 +1,43 @@ +package com.ionspin.kotlin.crypto.kdf + +import com.ionspin.kotlin.crypto.getSodium +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array + +actual object Kdf { + /** + * The deriveFromKey function derives a subkeyId-th subkey of length subkeyLenght bytes using + * the master key key and the context ctx. + * subkey_id can be any value up to (2^32) because javascript doesn't support long types. + * subkey_len has to be between crypto_kdf_BYTES_MIN (inclusive) and crypto_kdf_BYTES_MAX (inclusive). + * Similar to a type, the context ctx is a 8 characters string describing what the key is going to be used for. + * Its purpose is to mitigate accidental bugs by separating domains. The same function used with the same key but + * in two distinct contexts is likely to generate two different outputs. + * Contexts don't have to be secret and can have a low entropy. + * Examples of contexts include UserName, __auth__, pictures and userdata. + * They must be crypto_kdf_CONTEXTBYTES bytes long. + * If more convenient, it is also fine to use a single global context for a whole application. This will still + * prevent the same keys from being mistakenly used by another application. + */ + actual fun deriveFromKey( + subkeyId: UInt, + subkeyLength: Int, + context: String, + masterKey: UByteArray + ): UByteArray { + return getSodium().crypto_kdf_derive_from_key( + subkeyLength.toUInt(), + subkeyId, + context, + masterKey.toUInt8Array() + ).toUByteArray() + } + + /** + * The crypto_kdf_keygen() function creates a master key. + */ + actual fun keygen(): UByteArray { + return getSodium().crypto_kdf_keygen().toUByteArray() + } + +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/keyexchange/KeyExchange.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/keyexchange/KeyExchange.kt new file mode 100644 index 0000000..2f40309 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/keyexchange/KeyExchange.kt @@ -0,0 +1,57 @@ +package com.ionspin.kotlin.crypto.keyexchange + +import com.ionspin.kotlin.crypto.getSodium +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array +import org.khronos.webgl.Uint8Array + +actual object KeyExchange { + actual fun clientSessionKeys(clientPublicKey: UByteArray, clientSecretKey: UByteArray, serverPublicKey: UByteArray) : KeyExchangeSessionKeyPair { + + + val result = getSodium().crypto_kx_client_session_keys( + clientPublicKey.toUInt8Array(), + clientSecretKey.toUInt8Array(), + serverPublicKey.toUInt8Array() + ) + + val receiveKey = (result.sharedRx as Uint8Array).toUByteArray() + val sendKey = (result.sharedTx as Uint8Array).toUByteArray() + + + + return KeyExchangeSessionKeyPair(receiveKey, sendKey) + } + + actual fun keypair() : KeyExchangeKeyPair { + val result = getSodium().crypto_kx_keypair() + + val publicKey = (result.publicKey as Uint8Array).toUByteArray() + val secretKey = (result.privateKey as Uint8Array).toUByteArray() + + return KeyExchangeKeyPair(publicKey, secretKey) + } + + actual fun seedKeypair(seed: UByteArray) : KeyExchangeKeyPair { + val result = getSodium().crypto_kx_seed_keypair(seed.toUInt8Array()) + + val publicKey = (result.publicKey as Uint8Array).toUByteArray() + val secretKey = (result.privateKey as Uint8Array).toUByteArray() + + return KeyExchangeKeyPair(publicKey, secretKey) + } + + actual fun serverSessionKeys(serverPublicKey: UByteArray, serverSecretKey: UByteArray, clientPublicKey: UByteArray) : KeyExchangeSessionKeyPair { + + val result = getSodium().crypto_kx_server_session_keys( + serverPublicKey.toUInt8Array(), + serverSecretKey.toUInt8Array(), + clientPublicKey.toUInt8Array() + ) + + val receiveKey = (result.sharedRx as Uint8Array).toUByteArray() + val sendKey = (result.sharedTx as Uint8Array).toUByteArray() + + return KeyExchangeSessionKeyPair(receiveKey, sendKey) + } +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/pwhash/PasswordHash.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/pwhash/PasswordHash.kt new file mode 100644 index 0000000..82bf6c4 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/pwhash/PasswordHash.kt @@ -0,0 +1,98 @@ +package com.ionspin.kotlin.crypto.pwhash + +import com.ionspin.kotlin.crypto.getSodium +import com.ionspin.kotlin.crypto.util.encodeToUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array + +actual object PasswordHash { + /** + * The crypto_pwhash() function derives an outlen bytes long key from a password passwd whose length is passwdlen + * and a salt whose fixed length is crypto_pwhash_SALTBYTES bytes. passwdlen should be at least crypto_pwhash_ + * PASSWD_MIN and crypto_pwhash_PASSWD_MAX. outlen should be at least crypto_pwhash_BYTES_MIN = 16 (128 bits) and + * at most crypto_pwhash_BYTES_MAX. + * + * See https://libsodium.gitbook.io/doc/password_hashing/default_phf for more details + */ + actual fun pwhash( + outputLength: Int, + password: String, + salt: UByteArray, + opsLimit: ULong, + memLimit: Int, + algorithm: Int + ): UByteArray { + if (opsLimit > UInt.MAX_VALUE) { + throw RuntimeException("Javascript doesnt support more than ${UInt.MAX_VALUE} for opslimit") + } + return getSodium().crypto_pwhash( + outputLength.toUInt(), + password.encodeToUByteArray().toUInt8Array(), + salt.toUInt8Array(), + opsLimit.toUInt(), + memLimit.toUInt(), + algorithm.toUInt() + ).toUByteArray() + } + + /** + * The crypto_pwhash_str() function puts an ASCII encoded string into out, which includes: + * the result of a memory-hard, CPU-intensive hash function applied to the password passwd of length passwdlen + * the automatically generated salt used for the previous computation + * the other parameters required to verify the password, including the algorithm identifier, its version, opslimit and memlimit. + * out must be large enough to hold crypto_pwhash_STRBYTES bytes, but the actual output string may be shorter. + * The output string is zero-terminated, includes only ASCII characters and can be safely stored into SQL databases + * and other data stores. No extra information has to be stored in order to verify the password. + * The function returns 0 on success and -1 if it didn't complete successfully. + */ + actual fun str(password: String, opslimit: ULong, memlimit: Int): String { + if (opslimit > UInt.MAX_VALUE) { + throw RuntimeException("Javascript doesnt support more than ${UInt.MAX_VALUE} for opslimit") + } + return getSodium().crypto_pwhash_str( + password.encodeToUByteArray().toUInt8Array(), + opslimit.toUInt(), + memlimit.toUInt() + ) + } + + /** + * Check if a password verification string str matches the parameters opslimit and memlimit, and the current default algorithm. + * The function returns 1 if the string appears to be correct, but doesn't match the given parameters. In that situation, applications may want to compute a new hash using the current parameters the next time the user logs in. + * The function returns 0 if the parameters already match the given ones. + * It returns -1 on error. If it happens, applications may want to compute a correct hash the next time the user logs in. + */ + actual fun strNeedsRehash( + passwordHash: String, + opslimit: ULong, + memlimit: Int + ): Int { + if (opslimit > UInt.MAX_VALUE) { + throw RuntimeException("Javascript doesnt support more than ${UInt.MAX_VALUE} for opslimit") + } + return if ( + getSodium().crypto_pwhash_str_needs_rehash( + passwordHash, + opslimit.toUInt(), + memlimit.toUInt() + ) + ) { + 1 + } else { + 0 + } + } + + /** + * his function verifies that str is a valid password verification string (as generated by crypto_pwhash_str()) for passwd whose length is passwdlen. + * str has to be zero-terminated. + * It returns 0 if the verification succeeds, and -1 on error. + */ + actual fun strVerify(passwordHash: String, password: String): Boolean { + return getSodium().crypto_pwhash_str_verify( + passwordHash, + password.encodeToUByteArray().toUInt8Array() + ) + } + +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/scalarmult/ScalarMultiplication.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/scalarmult/ScalarMultiplication.kt new file mode 100644 index 0000000..ce98b3b --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/scalarmult/ScalarMultiplication.kt @@ -0,0 +1,40 @@ +package com.ionspin.kotlin.crypto.scalarmult + +import com.ionspin.kotlin.crypto.getSodium +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array + +actual object ScalarMultiplication { + /** + * This function can be used to compute a shared secret q given a user's secret key and another user's public key. + * n is crypto_scalarmult_SCALARBYTES bytes long, p and the output are crypto_scalarmult_BYTES bytes long. + * q represents the X coordinate of a point on the curve. As a result, the number of possible keys is limited to + * the group size (≈2^252), which is smaller than the key space. + * For this reason, and to mitigate subtle attacks due to the fact many (p, n) pairs produce the same result, + * using the output of the multiplication q directly as a shared key is not recommended. + * A better way to compute a shared key is h(q ‖ pk1 ‖ pk2), with pk1 and pk2 being the public keys. + * By doing so, each party can prove what exact public key they intended to perform a key exchange with + * (for a given public key, 11 other public keys producing the same shared secret can be trivially computed). + * This can be achieved with the following code snippet: + */ + actual fun scalarMultiplication(secretKeyN: UByteArray, publicKeyP: UByteArray): UByteArray { + val result = getSodium().crypto_scalarmult(secretKeyN.toUInt8Array(), publicKeyP.toUInt8Array()) + + return result.toUByteArray() + } + + /** + * Given a user's secret key n (crypto_scalarmult_SCALARBYTES bytes), the crypto_scalarmult_base() function + * computes the user's public key and puts it into q (crypto_scalarmult_BYTES bytes). + * crypto_scalarmult_BYTES and crypto_scalarmult_SCALARBYTES are provided for consistency, + * but it is safe to assume that crypto_scalarmult_BYTES == crypto_scalarmult_SCALARBYTES. + */ + actual fun scalarMultiplicationBase( + secretKeyN: UByteArray + ): UByteArray { + val result = getSodium().crypto_scalarmult_base( secretKeyN.toUInt8Array()) + + return result.toUByteArray() + } + +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/secretbox/SecretBox.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/secretbox/SecretBox.kt new file mode 100644 index 0000000..4ff9188 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/secretbox/SecretBox.kt @@ -0,0 +1,74 @@ +package com.ionspin.kotlin.crypto.secretbox + +import com.ionspin.kotlin.crypto.getSodium +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array +import org.khronos.webgl.Uint8Array + +actual object SecretBox { + actual fun easy(message: UByteArray, nonce: UByteArray, key: UByteArray): UByteArray { + return getSodium().crypto_secretbox_easy( + message.toUInt8Array(), + nonce.toUInt8Array(), + key.toUInt8Array() + ).toUByteArray() + } + + actual fun openEasy( + ciphertext: UByteArray, + nonce: UByteArray, + key: UByteArray + ): UByteArray { + try { + val decryptionResult = getSodium().crypto_secretbox_open_easy( + ciphertext.toUInt8Array(), + nonce.toUInt8Array(), + key.toUInt8Array() + ) + return (decryptionResult as Uint8Array).toUByteArray() + } catch (error: Throwable) { + throw SecretBoxCorruptedOrTamperedDataExceptionOrInvalidKey() + } + } + + actual fun detached( + message: UByteArray, + nonce: UByteArray, + key: UByteArray + ): SecretBoxEncryptedDataAndTag { + val result = getSodium().crypto_secretbox_detached( + message.toUInt8Array(), + nonce.toUInt8Array(), + key.toUInt8Array() + ) + return SecretBoxEncryptedDataAndTag( + (result.cipher as Uint8Array).toUByteArray(), + (result.mac as Uint8Array).toUByteArray() + ) + } + + actual fun openDetached( + ciphertext: UByteArray, + tag: UByteArray, + nonce: UByteArray, + key: UByteArray + ): UByteArray { + try { + val decryptionResult = getSodium().crypto_secretbox_open_detached( + ciphertext.toUInt8Array(), + tag.toUInt8Array(), + nonce.toUInt8Array(), + key.toUInt8Array() + ) + return (decryptionResult as Uint8Array).toUByteArray() + } catch (error: Throwable) { + throw SecretBoxCorruptedOrTamperedDataExceptionOrInvalidKey() + } + + } + + actual fun keygen(): UByteArray { + return getSodium().crypto_secretbox_keygen().toUByteArray() + } + +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/secretstream/SecretStream.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/secretstream/SecretStream.kt new file mode 100644 index 0000000..27856e8 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/secretstream/SecretStream.kt @@ -0,0 +1,58 @@ +package com.ionspin.kotlin.crypto.secretstream + +import com.ionspin.kotlin.crypto.getSodium +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array +import org.khronos.webgl.Uint8Array + +actual typealias SecretStreamState = Any + +actual object SecretStream { + actual fun xChaCha20Poly1305InitPush(key: UByteArray): SecretStreamStateAndHeader { + val state = getSodium().crypto_secretstream_xchacha20poly1305_init_push(key.toUInt8Array()) + return SecretStreamStateAndHeader(state.state, (state.header as Uint8Array).toUByteArray()) + } + + actual fun xChaCha20Poly1305Push( + state: SecretStreamState, + message: UByteArray, + associatedData: UByteArray, + tag: UByte + ): UByteArray { + return getSodium().crypto_secretstream_xchacha20poly1305_push( + state, message.toUInt8Array(), associatedData.toUInt8Array(), tag + ).toUByteArray() + } + + actual fun xChaCha20Poly1305InitPull( + key: UByteArray, + header: UByteArray + ): SecretStreamStateAndHeader { + val state = getSodium().crypto_secretstream_xchacha20poly1305_init_pull(header.toUInt8Array(), key.toUInt8Array()) + return SecretStreamStateAndHeader(state, header) + } + + actual fun xChaCha20Poly1305Pull( + state: SecretStreamState, + ciphertext: UByteArray, + associatedData: UByteArray + ): DecryptedDataAndTag { + val dataAndTag = getSodium().crypto_secretstream_xchacha20poly1305_pull( + state, ciphertext.toUInt8Array(), associatedData.toUInt8Array() + ) + if (dataAndTag == false) { + throw SecretStreamCorruptedOrTamperedDataException() + } + return DecryptedDataAndTag((dataAndTag.message as Uint8Array).toUByteArray(), dataAndTag.tag) + + } + + actual fun xChaCha20Poly1305Keygen(): UByteArray { + return getSodium().crypto_shorthash_keygen().toUByteArray() + } + + actual fun xChaCha20Poly1305Rekey(state: SecretStreamState) { + getSodium().crypto_secretstream_xchacha20poly1305_rekey(state) + } + +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/shortinputhash/ShortHash.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/shortinputhash/ShortHash.kt new file mode 100644 index 0000000..df47ed7 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/shortinputhash/ShortHash.kt @@ -0,0 +1,21 @@ +package com.ionspin.kotlin.crypto.shortinputhash + +import com.ionspin.kotlin.crypto.getSodium +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 21-Aug-2020 + */ +actual object ShortHash { + actual fun shortHash(data: UByteArray, key: UByteArray): UByteArray { + return getSodium().crypto_shorthash(data.toUInt8Array(), key.toUInt8Array()).toUByteArray() + } + + actual fun shortHashKeygen(): UByteArray { + return getSodium().crypto_shorthash_keygen().toUByteArray() + } + +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/signature/Signature.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/signature/Signature.kt new file mode 100644 index 0000000..6b6fb3b --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/signature/Signature.kt @@ -0,0 +1,161 @@ +package com.ionspin.kotlin.crypto.signature + +import com.ionspin.kotlin.crypto.getSodium +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array +import org.khronos.webgl.Uint8Array + +actual typealias SignatureState = Any + +actual object Signature { + actual fun init(): SignatureState { + return getSodium().crypto_sign_init() + } + + actual fun update(state: SignatureState, data: UByteArray) { + getSodium().crypto_sign_update(state, data.toUInt8Array()) + } + + actual fun finalCreate( + state: SignatureState, + secretKey: UByteArray + ): UByteArray { + return getSodium().crypto_sign_final_create( + state, + secretKey.toUInt8Array() + ).toUByteArray() + } + + actual fun finalVerify( + state: SignatureState, + signature: UByteArray, + publicKey: UByteArray + ) { + val verificationResult = getSodium().crypto_sign_final_verify( + state, + signature.toUInt8Array(), + publicKey.toUInt8Array() + ) + if (verificationResult == false) { + throw InvalidSignatureException() + } + } + + /** + * The crypto_sign_keypair() function randomly generates a secret key and a corresponding public key. + * The public key is put into pk (crypto_sign_PUBLICKEYBYTES bytes) and the secret key into sk (crypto_sign_SECRETKEYBYTES bytes). + */ + actual fun keypair(): SignatureKeyPair { + val keypair = getSodium().crypto_sign_keypair() + return SignatureKeyPair( + (keypair.publicKey as Uint8Array).toUByteArray(), + (keypair.privateKey as Uint8Array).toUByteArray() + ) + } + + /** + * The crypto_sign_keypair() function randomly generates a secret key and a corresponding public key. + * The public key is put into pk (crypto_sign_PUBLICKEYBYTES bytes) and the secret key into sk (crypto_sign_SECRETKEYBYTES bytes). + * Using crypto_sign_seed_keypair(), the key pair can also be deterministically derived from a single key seed (crypto_sign_SEEDBYTES bytes). + */ + actual fun seedKeypair(seed: UByteArray): SignatureKeyPair { + val keypair = getSodium().crypto_sign_seed_keypair(seed.toUInt8Array()) + return SignatureKeyPair( + (keypair.publicKey as Uint8Array).toUByteArray(), + (keypair.privateKey as Uint8Array).toUByteArray() + ) + } + + /** + * The crypto_sign() function prepends a signature to a message m whose length is mlen bytes, using the secret key sk. + * The signed message, which includes the signature + a plain copy of the message, is put into sm, and is crypto_sign_BYTES + mlen bytes long. + */ + actual fun sign(message: UByteArray, secretKey: UByteArray): UByteArray { + return getSodium().crypto_sign( + message.toUInt8Array(), + secretKey.toUInt8Array() + ).toUByteArray() + } + + /** + * The crypto_sign_open() function checks that the signed message sm whose length is smlen bytes has a valid signature for the public key pk. + * If the signature is doesn't appear to be valid, the function throws an exception + */ + actual fun open(signedMessage: UByteArray, publicKey: UByteArray): UByteArray { + try { + return getSodium().crypto_sign_open( + signedMessage.toUInt8Array(), + publicKey.toUInt8Array() + ).toUByteArray() + } catch (error : Throwable) { + throw InvalidSignatureException() + } + } + + /** + * In detached mode, the signature is stored without attaching a copy of the original message to it. + * The crypto_sign_detached() function signs the message m whose length is mlen bytes, using the secret key sk, + * and puts the signature into sig, which can be up to crypto_sign_BYTES bytes long. + */ + actual fun detached(message: UByteArray, secretKey: UByteArray): UByteArray { + return getSodium().crypto_sign_detached( + message.toUInt8Array(), + secretKey.toUInt8Array() + ).toUByteArray() + } + + /** + * The crypto_sign_verify_detached() function verifies that sig is a valid signature for the message m whose length + * is mlen bytes, using the signer's public key pk. + */ + actual fun verifyDetached(signature: UByteArray, message: UByteArray, publicKey: UByteArray) { + val verificationResult = getSodium().crypto_sign_verify_detached( + signature.toUInt8Array(), + message.toUInt8Array(), + publicKey.toUInt8Array() + ) + + if (verificationResult == false) { + throw InvalidSignatureException() + } + } + + /** + * The crypto_sign_ed25519_pk_to_curve25519() function converts an Ed25519 public key ed25519_pk to an X25519 public key and stores it into x25519_pk. + */ + actual fun ed25519PkToCurve25519(ed25519PublicKey: UByteArray): UByteArray { + return getSodium().crypto_sign_ed25519_pk_to_curve25519( + ed25519PublicKey.toUInt8Array() + ).toUByteArray() + } + + /** + * The crypto_sign_ed25519_sk_to_curve25519() function converts an Ed25519 secret key ed25519_sk to an X25519 secret key and stores it into x25519_sk. + */ + actual fun ed25519SkToCurve25519(ed25519SecretKey: UByteArray): UByteArray { + return getSodium().crypto_sign_ed25519_sk_to_curve25519( + ed25519SecretKey.toUInt8Array() + ).toUByteArray() + } + + /** + * The secret key actually includes the seed (either a random seed or the one given to crypto_sign_seed_keypair()) as well as the public key. + * While the public key can always be derived from the seed, the precomputation saves a significant amount of CPU cycles when signing. + */ + actual fun ed25519SkToSeed(secretKey: UByteArray): UByteArray { + return getSodium().crypto_sign_ed25519_sk_to_seed( + secretKey.toUInt8Array() + ).toUByteArray() + } + + /** + * The secret key actually includes the seed (either a random seed or the one given to crypto_sign_seed_keypair()) as well as the public key. + * While the public key can always be derived from the seed, the precomputation saves a significant amount of CPU cycles when signing. + */ + actual fun ed25519SkToPk(secretKey: UByteArray): UByteArray { + return getSodium().crypto_sign_ed25519_sk_to_pk( + secretKey.toUInt8Array() + ).toUByteArray() + } + +} \ No newline at end of file diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/stream/Stream.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/stream/Stream.kt new file mode 100644 index 0000000..35a9348 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/stream/Stream.kt @@ -0,0 +1,122 @@ +package com.ionspin.kotlin.crypto.stream + +import com.ionspin.kotlin.crypto.getSodium +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array + +actual object Stream { + actual fun chacha20(clen: Int, nonce: UByteArray, key: UByteArray): UByteArray { + //Note, unlike the other ones, here the positions of key and nonce are reversed. + val result = getSodium().crypto_stream_chacha20(clen.toUInt(), key.toUInt8Array(), nonce.toUInt8Array()) + + return result.toUByteArray() + } + + actual fun chacha20IetfXor( + message: UByteArray, + nonce: UByteArray, + key: UByteArray + ): UByteArray { + val result = getSodium().crypto_stream_chacha20_ietf_xor( + message.toUInt8Array(), + nonce.toUInt8Array(), + key.toUInt8Array() + ) + + return result.toUByteArray() + } + + actual fun chacha20IetfXorIc( + message: UByteArray, + nonce: UByteArray, + initialCounter: UInt, + key: UByteArray + ): UByteArray { + val result = getSodium().crypto_stream_chacha20_ietf_xor_ic( + message.toUInt8Array(), + nonce.toUInt8Array(), + initialCounter, + key.toUInt8Array() + ) + + return result.toUByteArray() + } + + actual fun chacha20Keygen(): UByteArray { + val result = getSodium().crypto_stream_chacha20_keygen() + + return result.toUByteArray() + } + + actual fun chacha20Xor( + message: UByteArray, + nonce: UByteArray, + key: UByteArray + ): UByteArray { + val result = getSodium().crypto_stream_chacha20_xor( + message.toUInt8Array(), + nonce.toUInt8Array(), + key.toUInt8Array() + ) + + return result.toUByteArray() + } + + actual fun chacha20XorIc( + message: UByteArray, + nonce: UByteArray, + initialCounter: ULong, + key: UByteArray + ): UByteArray { + if (initialCounter > UInt.MAX_VALUE) { + throw RuntimeException("Javascript doesnt support more than ${UInt.MAX_VALUE} for initial counter") + } + val result = getSodium().crypto_stream_chacha20_xor_ic( + message.toUInt8Array(), + nonce.toUInt8Array(), + initialCounter.toUInt(), + key.toUInt8Array() + ) + + + return result.toUByteArray() + } + +// actual fun xChacha20Keygen(): UByteArray { +// val result = getSodium().crypto_stream_xchacha20_keygen() +// +// return result.toUByteArray() +// } +// +// actual fun xChacha20Xor( +// message: UByteArray, +// nonce: UByteArray, +// key: UByteArray +// ): UByteArray { +// val result = getSodium().crypto_stream_xchacha20_xor( +// message.toUInt8Array(), +// nonce.toUInt8Array(), +// key.toUInt8Array() +// ) +// +// return result.toUByteArray() +// } +// +// actual fun xChacha20XorIc( +// message: UByteArray, +// nonce: UByteArray, +// initialCounter: ULong, +// key: UByteArray +// ): UByteArray { +// val result = getSodium().crypto_stream_xchacha20_xor_ic( +// message.toUInt8Array(), +// nonce.toUInt8Array(), +// initialCounter.toUInt(), +// key.toUInt8Array() +// ) +// +// return result.toUByteArray() +// } + + +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/util/LibsodiumRandom.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/util/LibsodiumRandom.kt new file mode 100644 index 0000000..1854710 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/util/LibsodiumRandom.kt @@ -0,0 +1,48 @@ +package com.ionspin.kotlin.crypto.util + +import com.ionspin.kotlin.crypto.getSodium +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 27-Sep-2020 + */ +actual object LibsodiumRandom { + /** + * The randombytes_buf() function fills size bytes starting at buf with an unpredictable sequence of bytes. + */ + actual fun buf(size: Int): UByteArray { + return getSodium().randombytes_buf(size).toUByteArray() + } + + /** + * The randombytes_buf_deterministic function stores size bytes into buf indistinguishable from random bytes without knowing seed. + * For a given seed, this function will always output the same sequence. size can be up to 2^31 (~8GB) because we use kotlin arrays + * and they are limited by Int primitive type + * seed is randombytes_SEEDBYTES bytes long. + * This function is mainly useful for writing tests, and was introduced in libsodium 1.0.12. Under the hood, it uses the ChaCha20 stream cipher. + * + */ + actual fun bufDeterministic(size: Int, seed: UByteArray): UByteArray { + return getSodium().randombytes_buf_deterministic(size.toUInt(), seed.toUInt8Array()).toUByteArray() + } + + /** + * The randombytes_random() function returns an unpredictable value between 0 and 0xffffffff (included). + */ + actual fun random(): UInt { + return getSodium().randombytes_random() + } + + /** + * The randombytes_uniform() function returns an unpredictable value between 0 and upper_bound (excluded). Unlike r + * andombytes_random() % upper_bound, it guarantees a uniform distribution of the possible output values even when + * upper_bound is not a power of 2. Note that an upper_bound < 2 leaves only a single element to be chosen, namely 0 + */ + actual fun uniform(upperBound: UInt): UInt { + return getSodium().randombytes_uniform(upperBound) + } + +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/util/LibsodiumUtil.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/util/LibsodiumUtil.kt new file mode 100644 index 0000000..e9c83fd --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/com/ionspin/kotlin/crypto/util/LibsodiumUtil.kt @@ -0,0 +1,50 @@ +package com.ionspin.kotlin.crypto.util + +import com.ionspin.kotlin.crypto.getSodium +import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array + +actual object LibsodiumUtil { + actual fun memcmp(first: UByteArray, second: UByteArray): Boolean { + return getSodium().memcmp(first.toUInt8Array(), second.toUInt8Array()) + } + + actual fun memzero(target: UByteArray) { + // libsodium.js does this as well, and theres no clear way at the moment of casting ubytearray to uint8array + // although I feel like there should be a way to work around it + (target.indices).forEach { + index -> target[index] = 0U + } + } + + actual fun pad(unpaddedData: UByteArray, blocksize: Int): UByteArray { + return getSodium().pad(unpaddedData.toUInt8Array(), blocksize).toUByteArray() + } + + actual fun unpad(paddedData: UByteArray, blocksize: Int): UByteArray { + return getSodium().unpad(paddedData.toUInt8Array(), blocksize).toUByteArray() + } + + actual fun toBase64( + data: UByteArray, + variant: Base64Variants + ): String { + return getSodium().to_base64(data.toUInt8Array(), variant.value) + } + + actual fun toHex(data: UByteArray): String { + return getSodium().to_hex(data.toUInt8Array()) + } + + actual fun fromBase64( + data: String, + variant: Base64Variants + ): UByteArray { + return getSodium().from_base64(data, variant.value).toUByteArray() + } + + actual fun fromHex(data: String): UByteArray { + return getSodium().from_hex(data).toUByteArray() + } + +} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/debug/test/DebugTest.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/debug/test/DebugTest.kt new file mode 100644 index 0000000..43b49bc --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/debug/test/DebugTest.kt @@ -0,0 +1,113 @@ +//package debug.test +// +//import com.ionspin.kotlin.crypto.getSodium +//import ext.libsodium.com.ionspin.kotlin.crypto.toUByteArray +//import ext.libsodium.com.ionspin.kotlin.crypto.toUInt8Array +//import kotlin.Any +//import kotlin.Int +//import kotlin.UByte +//import kotlin.UByteArray +//import org.khronos.webgl.Uint8Array +// +//actual typealias Sha256State = Any +// +//actual typealias Sha512State = Any +// +//actual typealias GenericHashState = Any +// +//actual typealias SecretStreamState = Any +// +//actual class Crypto internal actual constructor() { +// /** +// * Initialize the SHA256 hash +// * returns sha 256 state +// */ +// actual fun crypto_hash_sha256_init(): dynamic { +// println("Debug crypto_hash_sha256_init") +// val result = js("getSodium().crypto_hash_sha256_init()") +// return result +// } +// +// actual fun crypto_hash_sha256_update(state: Sha256State, input: UByteArray) { +// println("Debug crypto_hash_sha256_update") +// getSodium().crypto_hash_sha256_update(state, input.toUInt8Array()) +// } +// +// actual fun crypto_hash_sha256_final(state: Sha256State): UByteArray { +// println("Debug crypto_hash_sha256_final") +// return getSodium().crypto_hash_sha256_final(state).toUByteArray() +// } +// +// actual fun crypto_hash_sha512_init(): dynamic { +// println("Debug crypto_hash_sha512_init") +// val result = js("getSodium().crypto_hash_sha512_init()") +// return result +// } +// +// actual fun crypto_hash_sha512_update(state: Sha512State, input: UByteArray) { +// println("Debug crypto_hash_sha512_update") +// getSodium().crypto_hash_sha512_update(state, input.toUInt8Array()) +// } +// +// actual fun crypto_hash_sha512_final(state: Sha512State): UByteArray { +// println("Debug crypto_hash_sha512_final") +// return getSodium().crypto_hash_sha512_final(state).toUByteArray() +// } +// +// actual fun crypto_generichash_init(key: UByteArray, outlen: Int): dynamic { +// println("Debug crypto_generichash_init") +// return getSodium().crypto_generichash_init(key.toUInt8Array(), outlen) +// } +// +// /** +// * Initialize a state and generate a random header. Both are returned inside +// * `SecretStreamStateAndHeader` object. +// */ +// actual fun crypto_secretstream_xchacha20poly1305_init_push(key: UByteArray): +// SecretStreamStateAndHeader { +// println("Debug crypto_secretstream_xchacha20poly1305_init_push") +// val stateAndHeader = +// getSodium().crypto_secretstream_xchacha20poly1305_init_push(key.toUInt8Array()) +// val state = stateAndHeader.state +// val header = (stateAndHeader.header as Uint8Array).toUByteArray() +// return SecretStreamStateAndHeader(state, header) +// } +// +// /** +// * Initialize state from header and key. The state can then be used for decryption. +// */ +// actual fun crypto_secretstream_xchacha20poly1305_init_pull(header: UByteArray, key: UByteArray): +// dynamic { +// println("Debug crypto_secretstream_xchacha20poly1305_init_pull") +// return getSodium().crypto_secretstream_xchacha20poly1305_init_pull(header.toUInt8Array(), +// key.toUInt8Array()) +// } +// +// /** +// * Encrypt next block of data using the previously initialized state. Returns encrypted block. +// */ +// actual fun crypto_secretstream_xchacha20poly1305_push( +// state: SecretStreamState, +// m: UByteArray, +// ad: UByteArray, +// tag: UByte +// ): UByteArray { +// println("Debug crypto_secretstream_xchacha20poly1305_push") +// return getSodium().crypto_secretstream_xchacha20poly1305_push(state, m.toUInt8Array(), +// ad.toUInt8Array(), tag).toUByteArray() +// } +// +// /** +// * Decrypt next block of data using the previously initialized state. Returns decrypted block. +// */ +// actual fun crypto_secretstream_xchacha20poly1305_pull( +// state: SecretStreamState, +// c: UByteArray, +// ad: UByteArray +// ): DecryptedDataAndTag { +// println("Debug crypto_secretstream_xchacha20poly1305_pull") +//// return getSodium().crypto_secretstream_xchacha20poly1305_pull(state, c.toUInt8Array(), +//// ad.toUInt8Array()) +// return DecryptedDataAndTag(ubyteArrayOf(), 0U) +// } +//} diff --git a/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/libsodium.kt b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/libsodium.kt new file mode 100644 index 0000000..85ff707 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/wasmJsMain/kotlin/libsodium.kt @@ -0,0 +1,31 @@ +@file:JsModule("libsodium-sumo") +@file:JsNonModule +package ext.libsodium + +import org.khronos.webgl.Uint8Array +import kotlin.js.Promise + + +/** + * Created by Ugljesa Jovanovic + * ugljesa.jovanovic@ionspin.com + * on 25-May-2020 + */ + +@JsName("ready") +external val _libsodiumPromise : Promise + +@JsName("_sodium_init") +external fun sodium_init() : Int + +external fun crypto_generichash(hashLength: Int, inputMessage: Uint8Array) : Uint8Array + +external fun crypto_hash_sha256(message: Uint8Array) : Uint8Array +external fun crypto_hash_sha512(message: Uint8Array) : Uint8Array + +external fun crypto_hash_sha256_init(): dynamic + + + + +