From 531deb6a5764784ee7ac57367e3fb97450ef8e08 Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Sun, 21 Feb 2021 21:17:33 +0100 Subject: [PATCH] Added util jna --- README.md | 6 + .../util/LibsodiumUtil.kt | 3 + .../kotlin/crypto/JnaLibsodiumInterface.kt | 84 +++++++++ .../kotlin/crypto/util/LibsodiumUtil.kt | 177 ++++++++---------- .../{LibsodiumUtil.kt => LibsodiumUtilJvm.kt} | 0 5 files changed, 172 insertions(+), 98 deletions(-) rename multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/util/{LibsodiumUtil.kt => LibsodiumUtilJvm.kt} (100%) diff --git a/README.md b/README.md index 9220e94..3081649 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,12 @@ Currently supported native platforms: |minGW X86 32| :x: | +### Where do the compiled libraries used by JVM and Android come from +Android .so files come from running dist-build scripts in libsodium which you can find in the libsodium submodule +Java Linux Arm/X86_64 and Mac so and dylib are the same as produced by multiplatform builds, also based on the same submodule commit +Java Windows dll is from https://download.libsodium.org/libsodium/releases/libsodium-1.0.18-stable-msvc.zip + + ### TODO: - Copy/adapt code documentation, currently only some functions have documentation that is a copy-paste from libsodium website - Android testing diff --git a/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/util/LibsodiumUtil.kt b/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/util/LibsodiumUtil.kt index d8360f0..1a69d8f 100644 --- a/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/util/LibsodiumUtil.kt +++ b/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/util/LibsodiumUtil.kt @@ -10,6 +10,9 @@ enum class Base64Variants(val value: Int) { ORIGINAL(1), ORIGINAL_NO_PADDING(3), URLSAFE(5), URLSAFE_NO_PADDING(7) } +class ConversionException() : RuntimeException("Conversion failed") +class PaddingException() : RuntimeException("Padding failed") + expect object LibsodiumUtil { fun memcmp(first: UByteArray, second: UByteArray) : Boolean diff --git a/multiplatform-crypto-libsodium-bindings/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/JnaLibsodiumInterface.kt b/multiplatform-crypto-libsodium-bindings/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/JnaLibsodiumInterface.kt index 1496a43..e86e130 100644 --- a/multiplatform-crypto-libsodium-bindings/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/JnaLibsodiumInterface.kt +++ b/multiplatform-crypto-libsodium-bindings/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/JnaLibsodiumInterface.kt @@ -1,6 +1,7 @@ package com.ionspin.kotlin.crypto import com.sun.jna.Library +import com.sun.jna.Pointer import com.sun.jna.Structure /** @@ -61,10 +62,93 @@ class SecretStreamXChaCha20Poly1305State : Structure() { } interface JnaLibsodiumInterface : Library { + + // ---- Utils ---- fun sodium_version_string(): String fun randombytes_buf(buffer: ByteArray, bufferSize: Int) + // void sodium_memzero(void * const pnt, const size_t len); + fun sodium_memzero(array: ByteArray, len: Int) + + // int sodium_memcmp(const void * const b1_, const void * const b2_, size_t len) + fun sodium_memcmp(b1 : ByteArray, b2 : ByteArray, len: Int) : Int + + // char *sodium_bin2hex(char * const hex, const size_t hex_maxlen, + // const unsigned char * const bin, const size_t bin_len) + fun sodium_bin2hex( + hex: ByteArray, + hexMaxlen: Int, + bin: ByteArray, + binLen : Int + ) : String + + // int sodium_hex2bin( + // unsigned char * const bin, const size_t bin_maxlen, + // const char * const hex, const size_t hex_len, + // const char * const ignore, size_t * const bin_len, + // const char ** const hex_end) + fun sodium_hex2bin( + bin: ByteArray, + binMaxLength: Int, + hex: ByteArray, + hexLen: Int, + ignore: ByteArray?, + binLen: Pointer, + hexEnd: Pointer? + ) : Int + + // int sodium_pad(size_t *padded_buflen_p, unsigned char *buf, + // size_t unpadded_buflen, size_t blocksize, size_t max_buflen) + fun sodium_pad( + paddedBufferLength : Pointer, + buffer: ByteArray, + unpaddedBufferLength : Int, + blockSize: Int, + maxBufferLength: Int + ) : Int + + // int sodium_unpad(size_t *unpadded_buflen_p, const unsigned char *buf, + // size_t padded_buflen, size_t blocksize) + fun sodium_unpad( + unpaddedBufferLength: Pointer, + buffer: ByteArray, + paddedBufferLength: Int, + blockSize: Int + ) + + + // char *sodium_bin2base64(char * const b64, const size_t b64_maxlen, + // const unsigned char * const bin, const size_t bin_len, + // const int variant) + fun sodium_bin2base64( + base64: ByteArray, + base64MaxLength: Int, + bin: ByteArray, + binLength: Int, + variant: Int + ) : Int + + // int sodium_base642bin( + // unsigned char * const bin, const size_t bin_maxlen, + // const char * const b64, const size_t b64_len, + // const char * const ignore, size_t * const bin_len, + // const char ** const b64_end, const int variant) + fun sodium_base642bin( + bin : ByteArray, + binMaxLength: Int, + base64: ByteArray, + base64Length: Int, + ignore: ByteArray?, + binLength: Pointer, + base64End: Pointer?, + variant: Int + ) : Int + // size_t sodium_base64_encoded_len(const size_t bin_len, const int variant) + fun sodium_base64_encoded_len(binLength: Int, variant: Int) : Int + + // --- Utils end ---- + // int crypto_generichash(unsigned char *out, size_t outlen, // const unsigned char *in, unsigned long long inlen, // const unsigned char *key, size_t keylen) diff --git a/multiplatform-crypto-libsodium-bindings/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/util/LibsodiumUtil.kt b/multiplatform-crypto-libsodium-bindings/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/util/LibsodiumUtil.kt index a88bf78..7422108 100644 --- a/multiplatform-crypto-libsodium-bindings/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/util/LibsodiumUtil.kt +++ b/multiplatform-crypto-libsodium-bindings/src/jvmMain/kotlin/com/ionspin/kotlin/crypto/util/LibsodiumUtil.kt @@ -1,6 +1,7 @@ package com.ionspin.kotlin.crypto.util -import com.ionspin.kotlin.crypto.LibsodiumInitializer.sodium +import com.ionspin.kotlin.crypto.LibsodiumInitializer.sodiumJna +import com.sun.jna.ptr.IntByReference import java.lang.RuntimeException import java.util.* @@ -9,100 +10,63 @@ actual object LibsodiumUtil { if (first.size != second.size) { throw RuntimeException("Sodium memcmp() only supports comparing same length arrays") } - return sodium.sodium_memcmp(first.asByteArray(), second.asByteArray(), first.size) == 0 + return sodiumJna.sodium_memcmp(first.asByteArray(), second.asByteArray(), first.size) == 0 } actual fun memzero(target: UByteArray) { - sodium.sodium_memzero(target.asByteArray(), target.size) + sodiumJna.sodium_memzero(target.asByteArray(), target.size) } actual fun pad(unpaddedData: UByteArray, blocksize: Int): UByteArray { - // Pad is invalid in lazysodium-java because it uses char arrays, which are 2 bytes in java - // while libsodium expects 1 byte char array - // See https://github.com/terl/lazysodium-java/issues/85 - - //This is temporary solution until lazysodium is fixed - if (blocksize == 0) { - throw RuntimeException("Invalid block size: $blocksize") - } - val padAmount = blocksize - (unpaddedData.size % blocksize) - val result = if (padAmount == blocksize) { - unpaddedData + UByteArray(blocksize) { - when (it) { - 0 -> 0x80U - else -> 0x00U - } - } - } else { - unpaddedData + UByteArray(padAmount) { - when (it) { - 0 -> 0x80U - else -> 0x00U - } + val newPadSizeReference = IntByReference(0) + val newSize = ((unpaddedData.size / blocksize) + 1) * blocksize + val paddedArray = UByteArray(newSize) { + if (it < unpaddedData.size) { + unpaddedData[it] + } else { + 0U } } + val resultCode = sodiumJna.sodium_pad( + newPadSizeReference.pointer, + paddedArray.asByteArray(), + unpaddedData.size, + blocksize, + newSize + ) + if (resultCode != 0) { + throw RuntimeException("Padding failed") + } - return result - -// val newPadSizeReference = IntByReference(0) -// val newSize = ((unpaddedData.size / blocksize) + 1) * blocksize -// val charArray = CharArray(newSize) { -// if (it < unpaddedData.size) { -// unpaddedData[it].toByte().toChar() -// } else { -// '\u0000' -// } -// } -// sodium.sodium_pad( -// newPadSizeReference.pointer, -// charArray, -// unpaddedData.size + 1, -// blocksize, -// newSize -// ) -// -// return charArray.slice(0 until newPadSizeReference.value).map { it.toByte().toUByte()}.toUByteArray() + return paddedArray.slice(0 until newPadSizeReference.value).map { it.toByte().toUByte()}.toUByteArray() } actual fun unpad(paddedData: UByteArray, blocksize: Int): UByteArray { - // Pad is invalid in lazysodium-java because it uses char arrays, which are 2 bytes in java - // while libsodium expects 1 byte char array - // See https://github.com/terl/lazysodium-java/issues/85 + val paddedDataCopy = paddedData.copyOf().asByteArray() + var unpaddedSize = IntByReference(0) - //This is temporary solution until lazysodium is fixed + sodiumJna.sodium_unpad( + unpaddedSize.pointer, + paddedDataCopy, + paddedData.size, + blocksize + ) - val unpaddedData = paddedData.dropLastWhile { it == 0U.toUByte() } - if (unpaddedData.last() != 0x80U.toUByte()) { - throw RuntimeException("Invalid padding!") - } - return unpaddedData.dropLast(1).toUByteArray() + val unpadded = paddedDataCopy.sliceArray(0 until unpaddedSize.value).asUByteArray() - -// val paddedDataCopy = paddedData.copyOf().asByteArray().map { it.toChar() }.toCharArray() -// var unpaddedSize = IntByReference(0) -// -// sodium.sodium_unpad( -// unpaddedSize.pointer, -// paddedDataCopy, -// paddedData.size, -// blocksize -// ) -// -// val unpadded = paddedDataCopy.sliceArray(0 until unpaddedSize.value).map { it.toByte().toUByte() }.toUByteArray() -// -// return unpadded + return unpadded } actual fun toBase64( data: UByteArray, variant: Base64Variants ): String { - val maxlen = sodium.sodium_base64_encoded_len(data.size, variant.value) + val maxlen = sodiumJna.sodium_base64_encoded_len(data.size, variant.value) val result = ByteArray(maxlen) { 0 } - sodium.sodium_bin2base64( + sodiumJna.sodium_bin2base64( result, maxlen, data.asByteArray(), @@ -116,49 +80,66 @@ actual object LibsodiumUtil { actual fun toHex(data: UByteArray): String { val hexLen = (data.size * 2) + 1 // +1 for terminator char val result = ByteArray(hexLen) - sodium.sodium_bin2hex( + sodiumJna.sodium_bin2hex( result, hexLen, data.asByteArray(), data.size ) + //Drop terminating char \0 return String(result.sliceArray(0 until result.size - 1)) } + actual fun fromHex(data: String): UByteArray { + val binLenReference = IntByReference(0) + val binSize = (data.length + 1) / 2 // -1 for terminator char + val hex = data.toCharArray().map { it.toByte() }.toByteArray() + val result = ByteArray(binSize) + val resultCode = sodiumJna.sodium_hex2bin( + result, + binSize, + hex, + hex.size, + null, + binLenReference.pointer, + null + ) + if (resultCode != 0) { + throw ConversionException() + } + return result.slice(0 until binLenReference.value).toByteArray().asUByteArray() + } + actual fun fromBase64( data: String, variant: Base64Variants ): UByteArray { - // from base64 is currently broken in lazysodium-java - // see https://github.com/terl/lazysodium-java/issues/83 -// val maxLength = (data.length * 3) / 4 -// val intermediaryResult = UByteArray(maxLength) { 0U } -// -// sodium.sodium_base642bin( -// intermediaryResult.asByteArray(), -// maxLength, -// data.encodeToByteArray(), -// data.length, -// null, -// binLenPinned.addressOf(0), -// null, -// variant.value -// -// ) - val decoder = when(variant) { - Base64Variants.ORIGINAL -> Base64.getDecoder() - Base64Variants.ORIGINAL_NO_PADDING -> Base64.getDecoder() - Base64Variants.URLSAFE -> Base64.getUrlDecoder() - Base64Variants.URLSAFE_NO_PADDING -> Base64.getUrlDecoder() + val binLengthReference = IntByReference(0) + val maxLength = (data.length * 3) / 4 + val intermediaryResult = UByteArray(maxLength) { 0U } + + val resultCode = sodiumJna.sodium_base642bin( + intermediaryResult.asByteArray(), + maxLength, + data.encodeToByteArray(), + data.length, + null, + binLengthReference.pointer, + null, + variant.value + ) + + if (resultCode != 0) { + throw ConversionException() } - return decoder.decode(data).asUByteArray() + + return intermediaryResult.sliceArray(0 until binLengthReference.value) + + + } - actual fun fromHex(data: String): UByteArray { - // from hex is currently broken in lazysodium-java - // see https://github.com/terl/lazysodium-java/issues/83 - return data.hexStringToUByteArray() - } + } diff --git a/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/util/LibsodiumUtil.kt b/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/util/LibsodiumUtilJvm.kt similarity index 100% rename from multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/util/LibsodiumUtil.kt rename to multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/util/LibsodiumUtilJvm.kt