From b0ce2e10fcec2f3f41d692a45d08289ce20d6a4d Mon Sep 17 00:00:00 2001 From: Ugljesa Jovanovic Date: Mon, 14 Sep 2020 18:51:25 +0200 Subject: [PATCH] Adding _sign_ native implementation and started writing tests --- .../signature/Signature.kt | 22 +- .../kotlin/crypto/signature/SignatureTest.kt | 32 +++ .../kotlin/crypto/signature/Signature.kt | 268 ++++++++++++++++++ 3 files changed, 315 insertions(+), 7 deletions(-) create mode 100644 multiplatform-crypto-libsodium-bindings/src/commonTest/kotlin/com/ionspin/kotlin/crypto/signature/SignatureTest.kt create mode 100644 multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/signature/Signature.kt diff --git a/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/signature/Signature.kt b/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/signature/Signature.kt index 4a9c70b..4f7a22a 100644 --- a/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/signature/Signature.kt +++ b/multiplatform-crypto-libsodium-bindings/src/commonMain/kotlin/com.ionspin.kotlin.crypto/signature/Signature.kt @@ -5,12 +5,13 @@ package com.ionspin.kotlin.crypto.signature */ expect class SignatureState -data class SignKeyPair(val publicKey: UByteArray, val secretKey: UByteArray) +data class SignatureKeyPair(val publicKey: UByteArray, val secretKey: UByteArray) const val crypto_sign_BYTES = 64 const val crypto_sign_SEEDBYTES = 32 const val crypto_sign_PUBLICKEYBYTES = 32 -const val crypto_sign_SECRETKEY2BYTES = 64 +const val crypto_sign_SECRETKEYBYTES = 64 +const val crypto_scalarmult_curve25519_BYTES = 32 class InvalidSignatureException() : RuntimeException("Signature validation failed") @@ -24,14 +25,14 @@ expect object Signature { * 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). */ - fun keypair(): SignKeyPair + fun keypair(): SignatureKeyPair /** * 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). */ - fun seedKeypair(seed: UByteArray): SignKeyPair + fun seedKeypair(seed: UByteArray): SignatureKeyPair /** * The crypto_sign() function prepends a signature to a message m whose length is mlen bytes, using the secret key sk. @@ -56,9 +57,16 @@ expect object Signature { * 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. */ - fun verifyDetached(signature: UByteArray, message: UByteArray, publicKey: UByteArray): Boolean - fun ed25519PkToCurve25519() - fun ed25519SkToCurve25519() + fun verifyDetached(signature: UByteArray, message: UByteArray, publicKey: UByteArray) + /** + * 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. + */ + fun ed25519PkToCurve25519(ed25519PublicKey: UByteArray) : UByteArray + + /** + * 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. + */ + fun ed25519SkToCurve25519(ed25519SecretKey: UByteArray) : UByteArray /** * 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. diff --git a/multiplatform-crypto-libsodium-bindings/src/commonTest/kotlin/com/ionspin/kotlin/crypto/signature/SignatureTest.kt b/multiplatform-crypto-libsodium-bindings/src/commonTest/kotlin/com/ionspin/kotlin/crypto/signature/SignatureTest.kt new file mode 100644 index 0000000..508dbf2 --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/commonTest/kotlin/com/ionspin/kotlin/crypto/signature/SignatureTest.kt @@ -0,0 +1,32 @@ +package com.ionspin.kotlin.crypto.signature + +import com.ionspin.kotlin.crypto.LibsodiumInitializer +import com.ionspin.kotlin.crypto.util.encodeToUByteArray +import kotlin.test.Test +import kotlin.test.assertFailsWith +import kotlin.test.assertTrue + +/** + * Created by Ugljesa Jovanovic (jovanovic.ugljesa@gmail.com) on 14/Sep/2020 + */ +class SignatureTest { + + @Test + fun testSignAndVerify() { + LibsodiumInitializer.initializeWithCallback { + val keys = Signature.keypair() + val message = "Some text that will be signed".encodeToUByteArray() + val signedMessage = Signature.sign(message, keys.secretKey) + val verifiedMessage = Signature.open(signedMessage, keys.publicKey) + assertTrue { + verifiedMessage.contentEquals(message) + } + assertFailsWith(InvalidSignatureException::class) { + val tamperedMessage = signedMessage.copyOf() + tamperedMessage[crypto_sign_BYTES + 1] = 0U + Signature.open(tamperedMessage, keys.publicKey) + } + } + + } +} \ No newline at end of file diff --git a/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/signature/Signature.kt b/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/signature/Signature.kt new file mode 100644 index 0000000..69a1dcc --- /dev/null +++ b/multiplatform-crypto-libsodium-bindings/src/nativeMain/kotlin/com/ionspin/kotlin/crypto/signature/Signature.kt @@ -0,0 +1,268 @@ +package com.ionspin.kotlin.crypto.signature + +import com.ionspin.kotlin.crypto.util.toPtr +import kotlinx.cinterop.* +import libsodium.* +import platform.posix.malloc + +actual typealias SignatureState = crypto_sign_ed25519ph_state + +actual object Signature { + actual fun init(): SignatureState { + val stateAllocated = malloc(SignatureState.size.convert()) + val statePointed = stateAllocated!!.reinterpret().pointed + crypto_sign_init(statePointed.ptr) + return statePointed + } + + actual fun update(state: SignatureState, data: UByteArray) { + val dataPinned = data.pin() + crypto_sign_update(state.ptr, dataPinned.toPtr(), data.size.convert()) + dataPinned.unpin() + } + + actual fun finalCreate( + state: SignatureState, + secretKey: UByteArray + ): UByteArray { + val signature = UByteArray(crypto_sign_BYTES) + val secretKeyPinned = secretKey.pin() + val signaturePinned = signature.pin() + crypto_sign_final_create( + state.ptr, + signaturePinned.toPtr(), + null, + secretKeyPinned.toPtr() + ) + secretKeyPinned.unpin() + signaturePinned.unpin() + return signature + } + + actual fun finalVerify( + state: SignatureState, + signature: UByteArray, + publicKey: UByteArray + ) { + val signaturePinned = signature.pin() + val publicKeyPinned = publicKey.pin() + val verificationResult = crypto_sign_final_verify( + state.ptr, + signaturePinned.toPtr(), + publicKeyPinned.toPtr() + ) + if (verificationResult == -1) { + 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 publicKey = UByteArray(crypto_sign_PUBLICKEYBYTES) + val secretKey = UByteArray(crypto_sign_SECRETKEYBYTES) + val publicKeyPinned = publicKey.pin() + val secretKeyPinned = secretKey.pin() + crypto_sign_keypair( + publicKeyPinned.toPtr(), + secretKeyPinned.toPtr(), + ) + publicKeyPinned.unpin() + secretKeyPinned.unpin() + return SignatureKeyPair(publicKey, secretKey) + } + + /** + * 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 seedPinned = seed.pin() + val publicKey = UByteArray(crypto_sign_PUBLICKEYBYTES) + val secretKey = UByteArray(crypto_sign_SECRETKEYBYTES) + val publicKeyPinned = publicKey.pin() + val secretKeyPinned = secretKey.pin() + crypto_sign_seed_keypair( + publicKeyPinned.toPtr(), + secretKeyPinned.toPtr(), + seedPinned.toPtr() + ) + seedPinned.unpin() + publicKeyPinned.unpin() + secretKeyPinned.unpin() + return SignatureKeyPair(publicKey, secretKey) + } + + /** + * 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 { + val signedMessage = UByteArray(message.size + crypto_sign_BYTES) + val signedMessagePinned = signedMessage.pin() + val messagePinned = message.pin() + val secretKeyPinned = secretKey.pin() + crypto_sign( + signedMessagePinned.toPtr(), + null, + messagePinned.toPtr(), + message.size.convert(), + secretKeyPinned.toPtr() + ) + signedMessagePinned.unpin() + messagePinned.unpin() + secretKeyPinned.unpin() + + return signedMessage + } + + /** + * 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 { + val message = UByteArray(signedMessage.size - crypto_sign_BYTES) + val messagePinned = message.pin() + val signedMessagePinned = signedMessage.pin() + val publicKeyPinned = publicKey.pin() + + val verificationResult = crypto_sign_open( + messagePinned.toPtr(), + null, + signedMessagePinned.toPtr(), + signedMessage.size.convert(), + publicKeyPinned.toPtr() + ) + if (verificationResult == -1) { + throw InvalidSignatureException() + } + return message + } + + /** + * 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 { + val signature = UByteArray(crypto_sign_BYTES) + val signaturePinned = signature.pin() + val messagePinned = message.pin() + val secretKeyPinned = secretKey.pin() + crypto_sign_detached( + signaturePinned.toPtr(), + null, + messagePinned.toPtr(), + message.size.convert(), + secretKeyPinned.toPtr() + ) + signaturePinned.unpin() + messagePinned.unpin() + secretKeyPinned.unpin() + + return signature + } + + /** + * 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 signaturePinned = signature.pin() + val messagePinned = message.pin() + val publicKeyPinned = publicKey.pin() + val verificationResult = crypto_sign_verify_detached( + signaturePinned.toPtr(), + messagePinned.toPtr(), + message.size.convert(), + publicKeyPinned.toPtr() + ) + signaturePinned.unpin() + messagePinned.unpin() + publicKeyPinned.unpin() + + if (verificationResult == -1) { + 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 { + val x25519PublicKey = UByteArray(crypto_scalarmult_curve25519_BYTES) + val x25519PublicKeyPinned = x25519PublicKey.pin() + val ed25519PublicKeyPinned = ed25519PublicKey.pin() + crypto_sign_ed25519_sk_to_curve25519( + x25519PublicKeyPinned.toPtr(), + ed25519PublicKeyPinned.toPtr() + ) + x25519PublicKeyPinned.unpin() + ed25519PublicKeyPinned.unpin() + return x25519PublicKey + } + + actual fun ed25519SkToCurve25519(ed25519SecretKey: UByteArray) : UByteArray { + val x25519SecretKey = UByteArray(crypto_scalarmult_curve25519_BYTES) + val x25519SecretKeyPinned = x25519SecretKey.pin() + val ed25519SecretKeyPinned = ed25519SecretKey.pin() + crypto_sign_ed25519_sk_to_curve25519( + x25519SecretKeyPinned.toPtr(), + ed25519SecretKeyPinned.toPtr() + ) + x25519SecretKeyPinned.unpin() + ed25519SecretKeyPinned.unpin() + return x25519SecretKey + } + + /** + * 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 { + val seed = UByteArray(crypto_sign_SEEDBYTES) + + val secretKeyPinned = secretKey.pin() + val seedPinned = seed.pin() + + crypto_sign_ed25519_sk_to_seed( + seedPinned.toPtr(), + secretKeyPinned.toPtr() + ) + + secretKeyPinned.unpin() + seedPinned.unpin() + + return seed + + } + + /** + * 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 { + val publicKey = UByteArray(crypto_sign_PUBLICKEYBYTES) + + val secretKeyPinned = secretKey.pin() + val publicKeyPinned = publicKey.pin() + + crypto_sign_ed25519_sk_to_pk( + publicKeyPinned.toPtr(), + secretKeyPinned.toPtr() + ) + + secretKeyPinned.unpin() + publicKeyPinned.unpin() + + return publicKey + } + +} \ No newline at end of file