Foundations(iOS Sourceset): Provide implementation for AESGCM. Duplicate AESGCM tests for iOS. Move dependency to 'cryptography-optimal' make use of all iOS crypto providers.

This commit is contained in:
KotlinGeekDev
2026-02-23 17:57:54 +01:00
parent 460be8cbf3
commit 265fef0a93
8 changed files with 159 additions and 14 deletions
+1 -1
View File
@@ -119,7 +119,7 @@ coil-gif = { group = "io.coil-kt.coil3", name = "coil-gif", version.ref = "coil"
coil-svg = { group = "io.coil-kt.coil3", name = "coil-svg", version.ref = "coil" } coil-svg = { group = "io.coil-kt.coil3", name = "coil-svg", version.ref = "coil" }
coil-okhttp = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil" } coil-okhttp = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil" }
com-ditchoom-buffer-compression = { module = "com.ditchoom:buffer-compression", version.ref = "comDitchoomBufferCompression" } com-ditchoom-buffer-compression = { module = "com.ditchoom:buffer-compression", version.ref = "comDitchoomBufferCompression" }
dev-whyoleg-cryptography-provider-apple = { module = "dev.whyoleg.cryptography:cryptography-provider-apple", version.ref = "devWhyolegCryptography" } dev-whyoleg-cryptography-provider-apple-optimal = { module = "dev.whyoleg.cryptography:cryptography-provider-optimal", version.ref = "devWhyolegCryptography" }
drfonfon-geohash = { group = "com.github.drfonfon", name = "android-kotlin-geohash", version.ref = "androidKotlinGeohash" } drfonfon-geohash = { group = "com.github.drfonfon", name = "android-kotlin-geohash", version.ref = "androidKotlinGeohash" }
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" } firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }
firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging" } firebase-messaging = { group = "com.google.firebase", name = "firebase-messaging" }
+1 -1
View File
@@ -230,7 +230,7 @@ kotlin {
implementation(libs.charlietap.cachemap) implementation(libs.charlietap.cachemap)
implementation(libs.net.thauvin.erik.urlencoder.lib) implementation(libs.net.thauvin.erik.urlencoder.lib)
implementation(libs.com.ditchoom.buffer.compression) implementation(libs.com.ditchoom.buffer.compression)
implementation(libs.dev.whyoleg.cryptography.provider.apple) implementation(libs.dev.whyoleg.cryptography.provider.apple.optimal)
} }
} }
@@ -20,23 +20,47 @@
*/ */
package com.vitorpamplona.quartz.utils.ciphers package com.vitorpamplona.quartz.utils.ciphers
import com.vitorpamplona.quartz.utils.Log
import dev.whyoleg.cryptography.CryptographyProvider
import dev.whyoleg.cryptography.DelicateCryptographyApi
import dev.whyoleg.cryptography.algorithms.AES
actual class AESGCM actual constructor( actual class AESGCM actual constructor(
actual val keyBytes: ByteArray, actual val keyBytes: ByteArray,
actual val nonce: ByteArray, actual val nonce: ByteArray,
) : NostrCipher { ) : NostrCipher {
actual override fun name(): String { private val provider = CryptographyProvider.Default
TODO("Not yet implemented") private val aesGcm = provider.get(AES.GCM)
private val keyDecoder =
aesGcm
.keyDecoder()
.decodeFromByteArrayBlocking(AES.Key.Format.RAW, keyBytes)
private fun cipher() = keyDecoder.cipher()
actual override fun name(): String = "aes-gcm"
@OptIn(DelicateCryptographyApi::class)
actual override fun encrypt(bytesToEncrypt: ByteArray): ByteArray =
with(cipher()) {
encryptWithIvBlocking(nonce, bytesToEncrypt)
} }
actual override fun encrypt(bytesToEncrypt: ByteArray): ByteArray { @OptIn(DelicateCryptographyApi::class)
TODO("Not yet implemented") actual override fun decrypt(bytesToDecrypt: ByteArray): ByteArray =
with(cipher()) {
decryptWithIvBlocking(nonce, bytesToDecrypt)
} }
actual override fun decrypt(bytesToDecrypt: ByteArray): ByteArray { @OptIn(DelicateCryptographyApi::class)
TODO("Not yet implemented") actual override fun decryptOrNull(bytesToDecrypt: ByteArray): ByteArray? =
try {
with(cipher()) {
decryptWithIvBlocking(nonce, bytesToDecrypt)
} }
} catch (e: Exception) {
actual override fun decryptOrNull(bytesToDecrypt: ByteArray): ByteArray? { Log.w("AESGCM", "Failed to decrypt", e)
TODO("Not yet implemented") null
} }
} }
@@ -20,10 +20,15 @@
*/ */
package com.vitorpamplona.quartz package com.vitorpamplona.quartz
import dev.whyoleg.cryptography.CryptographyProviderApi
import dev.whyoleg.cryptography.providers.base.toByteArray
import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.toKString import kotlinx.cinterop.toKString
import kotlinx.io.files.FileNotFoundException
import platform.Foundation.NSData
import platform.Foundation.NSString import platform.Foundation.NSString
import platform.Foundation.NSUTF8StringEncoding import platform.Foundation.NSUTF8StringEncoding
import platform.Foundation.dataWithContentsOfFile
import platform.Foundation.stringWithContentsOfFile import platform.Foundation.stringWithContentsOfFile
import platform.posix.getenv import platform.posix.getenv
@@ -34,4 +39,11 @@ actual class TestResourceLoader {
val filePath = "$resourceDir/$file" val filePath = "$resourceDir/$file"
return NSString.stringWithContentsOfFile(filePath, encoding = NSUTF8StringEncoding, error = null) as String return NSString.stringWithContentsOfFile(filePath, encoding = NSUTF8StringEncoding, error = null) as String
} }
@OptIn(ExperimentalForeignApi::class, CryptographyProviderApi::class)
fun loadFileData(file: String): ByteArray {
val resourceDir = getenv("TEST_RESOURCES_ROOT")?.toKString()
val filePath = "$resourceDir/$file"
return NSData.dataWithContentsOfFile(filePath)?.toByteArray() ?: throw FileNotFoundException("Resource $file was not found.")
}
} }
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip17Dm
import com.vitorpamplona.quartz.TestResourceLoader
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
import com.vitorpamplona.quartz.utils.ciphers.AESGCM
import kotlin.test.Test
import kotlin.test.assertEquals
class AESGCMTest {
val decryptionNonce = "01e77c94bd5aba3e3cbb69594e7ba07c"
val decryptionKey = "c128ecffab90ee7810e3df08e7fb2cc39a8d40f24201f48b2b36e23b34ac50ee"
val cipher =
AESGCM(
decryptionKey.hexToByteArray(),
decryptionNonce.encodeToByteArray(),
)
@Test
fun encryptDecrypt() {
val encrypted = cipher.encrypt("Testing".encodeToByteArray())
val decrypted = cipher.decrypt(encrypted)
assertEquals("Testing", decrypted.decodeToString())
}
@Test
fun imageTest() {
val image =
TestResourceLoader().loadFileData("ovxxk2vz.jpg")
val decrypted = cipher.decrypt(image)
assertEquals(44201, decrypted.size)
}
@Test
fun videoTest2() {
val myCipher =
AESGCM(
"373d19850ebc8ed5b0fefcca5cd6f27fde9cb6ac54fd32f6b4fad9d68ebe8ee0".hexToByteArray(),
"95e67b6874784a54299b58b8990499bd".hexToByteArray(),
)
val encrypted =
TestResourceLoader().loadFileData("trouble_video")
val decrypted = myCipher.decrypt(encrypted)
assertEquals(1277122, decrypted.size)
}
}
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip17Dm
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
import kotlinx.collections.immutable.persistentSetOf
import kotlin.test.Test
import kotlin.test.assertEquals
class ChatroomKeyTest {
@Test
fun testEquals() {
val k1 = ChatroomKey(persistentSetOf("Key1", "Key2"))
val k2 = ChatroomKey(persistentSetOf("Key1", "Key2"))
assertEquals(k1, k2)
assertEquals(k1.hashCode(), k2.hashCode())
}
}
Binary file not shown.
Binary file not shown.