From 2b86a54790075f1f8c169c41daca40fb9ccfa877 Mon Sep 17 00:00:00 2001 From: Jacob Persson <7156+typfel@users.noreply.github.com> Date: Thu, 21 Nov 2024 17:23:18 +0100 Subject: [PATCH 1/2] refactor: the Kotlin wrapper to be in line with the Web wrapper The Kotlin wrapper was at some point imported from the Kalium but for us it makes more sense to align all our wrapper to have a similar structure. --- .../com/wire/crypto/client/CoreCrypto.kt | 125 +++++ .../wire/crypto/client/CoreCryptoCentral.kt | 351 ------------ .../wire/crypto/client/CoreCryptoContext.kt | 2 +- .../com/wire/crypto/client/E2eiClient.kt | 7 +- .../com/wire/crypto/client/MLSClient.kt | 518 ------------------ .../com/wire/crypto/client/ProteusClient.kt | 229 -------- .../com/wire/crypto/client/ProteusModel.kt | 44 ++ .../kotlin/com/wire/crypto/client/E2EITest.kt | 22 +- .../kotlin/com/wire/crypto/client/MLSTest.kt | 184 ++++--- .../wire/crypto/client/ProteusClientTest.kt | 49 +- 10 files changed, 307 insertions(+), 1224 deletions(-) create mode 100644 crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/CoreCrypto.kt delete mode 100644 crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/CoreCryptoCentral.kt delete mode 100644 crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/MLSClient.kt delete mode 100644 crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/ProteusClient.kt create mode 100644 crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/ProteusModel.kt diff --git a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/CoreCrypto.kt b/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/CoreCrypto.kt new file mode 100644 index 0000000000..69a3d9e8c5 --- /dev/null +++ b/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/CoreCrypto.kt @@ -0,0 +1,125 @@ +package com.wire.crypto.client + +import com.wire.crypto.* +import java.util.* +import kotlin.reflect.typeOf + +typealias EnrollmentHandle = ByteArray + +/** + * Initializes the logging inside Core Crypto. Not required to be called and by default there will be no logging. + * + * @param logger a callback to implement the platform specific logging. It will receive the string with the log text from Core Crypto + **/ +fun setLogger(logger: CoreCryptoLogger) { + com.wire.crypto.setLoggerOnly(logger) +} + +/** + * Set maximum log level of logs which are forwarded to the [CoreCryptoLogger]. + * + * @param level the max level that should be logged, by default it will be WARN + */ +fun setMaxLogLevel(level: CoreCryptoLogLevel) { + com.wire.crypto.setMaxLogLevel(level) +} + +class CoreCrypto(val cc: com.wire.crypto.CoreCrypto) { + + companion object { + internal const val DEFAULT_NB_KEY_PACKAGE: UInt = 100U + + suspend operator fun invoke( + keystore: String, + databaseKey: String + ): CoreCrypto { + val cc = coreCryptoDeferredInit(keystore, databaseKey) + cc.setCallbacks(Callbacks()) + return CoreCrypto(cc) + } + } + + internal fun lower() = cc + + /** + * Starts a transaction in Core Crypto. If the callback succeeds, it will be committed, otherwise, every operation + * performed with the context will be discarded. + * + * @param block the function to be executed within the transaction context. A [CoreCryptoContext] will be given as parameter to this function + * + * @return the return of the function passed as parameter + */ + @Suppress("unchecked_cast") + suspend fun transaction(block: suspend (context: CoreCryptoContext) -> R): R { + var result: R? = null + var error: Throwable? = null + try { + this.cc.transaction(object : CoreCryptoCommand { + override suspend fun execute(context: com.wire.crypto.CoreCryptoContext) { + try { + result = block(CoreCryptoContext(context)) + } catch (e: Throwable) { + // We want to catch the error before it gets wrapped by core crypto. + error = e + // This is to tell core crypto that there was an error inside the transaction. + throw e + } + } + }) + // Catch the wrapped error, which we don't need, because we caught the original error above. + } catch (_: Throwable) { } + if (error != null) { + throw error as Throwable + } + + // Since we know that transaction will either run or throw it's safe to do unchecked cast here + return result as R + } + + suspend fun proteusInit() { + cc.proteusInit() + } + + /** + * Dumps the PKI environment as PEM + * + * @return a struct with different fields representing the PKI environment as PEM strings + */ + suspend fun e2eiDumpPKIEnv(): E2eiDumpedPkiEnv? { + return cc.e2eiDumpPkiEnv() + } + + /** + * Returns whether the E2EI PKI environment is setup (i.e. Root CA, Intermediates, CRLs) + */ + suspend fun e2eiIsPKIEnvSetup(): Boolean { + return cc.e2eiIsPkiEnvSetup() + } + + /** + * Closes this [CoreCryptoCentral] instance and deallocates all loaded resources. + * + * **CAUTION**: This {@link CoreCrypto} instance won't be usable after a call to this method, but there's no way to express this requirement in Kotlin, so you'll get errors instead! + */ + fun close() { + cc.close() + } +} + +private class Callbacks : CoreCryptoCallbacks { + + override suspend fun authorize(conversationId: ByteArray, clientId: ByteArray): Boolean = true + + override suspend fun userAuthorize( + conversationId: ByteArray, + externalClientId: ByteArray, + existingClients: List + ): Boolean = true + + override suspend fun clientIsExistingGroupUser( + conversationId: ByteArray, + clientId: ByteArray, + existingClients: List, + parentConversationClients: List? + ): Boolean = true +} diff --git a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/CoreCryptoCentral.kt b/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/CoreCryptoCentral.kt deleted file mode 100644 index 551076b525..0000000000 --- a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/CoreCryptoCentral.kt +++ /dev/null @@ -1,351 +0,0 @@ -package com.wire.crypto.client - -import com.wire.crypto.* -import com.wire.crypto.CoreCryptoCallbacks -import java.io.File - -typealias EnrollmentHandle = ByteArray - -private class Callbacks : CoreCryptoCallbacks { - - override suspend fun authorize(conversationId: ByteArray, clientId: ByteArray): Boolean = true - - override suspend fun userAuthorize( - conversationId: ByteArray, - externalClientId: ByteArray, - existingClients: List - ): Boolean = true - - override suspend fun clientIsExistingGroupUser( - conversationId: ByteArray, - clientId: ByteArray, - existingClients: List, - parentConversationClients: List? - ): Boolean = true -} - -/** - * Starts a transaction in Core Crypto. If the callback succeeds, it will be committed, otherwise, every operation - * performed with the context will be discarded. - * - * @param block the function to be executed within the transaction context. A [CoreCryptoContext] will be given as parameter to this function - * - * @return the return of the function passed as parameter - */ -suspend fun CoreCryptoCentral.transaction(block: suspend (context: CoreCryptoContext) -> R): R? { - var result: R? = null - var error: Throwable? = null - try { - this.cc.transaction(object : CoreCryptoCommand { - override suspend fun execute(context: com.wire.crypto.CoreCryptoContext) { - try { - result = block(CoreCryptoContext(context)) - } catch (e: Throwable) { - // We want to catch the error before it gets wrapped by core crypto. - error = e - // This is to tell core crypto that there was an error inside the transaction. - throw e - } - } - }) - // Catch the wrapped error, which we don't need, because we caught the original error above. - } catch (_: Throwable) {} - if (error != null) { - throw error as Throwable - } - return result -} - -/** - * Initializes the logging inside Core Crypto. Not required to be called and by default there will be no logging. - * - * @param logger a callback to implement the platform specific logging. It will receive the string with the log text from Core Crypto - * @param level the max level that should be logged - **/ -@Deprecated("Use setLogger and setMaxLogLevel instead") -fun initLogger(logger: CoreCryptoLogger, level: CoreCryptoLogLevel) { - com.wire.crypto.setLoggerOnly(logger) - com.wire.crypto.setMaxLogLevel(level) -} - -/** - * Initializes the logging inside Core Crypto. Not required to be called and by default there will be no logging. - * - * @param logger a callback to implement the platform specific logging. It will receive the string with the log text from Core Crypto - **/ -fun setLogger(logger: CoreCryptoLogger) { - com.wire.crypto.setLoggerOnly(logger) -} - -/** - * Set maximum log level of logs which are forwarded to the [CoreCryptoLogger]. - * - * @param level the max level that should be logged, by default it will be WARN - */ -fun setMaxLogLevel(level: CoreCryptoLogLevel) { - com.wire.crypto.setMaxLogLevel(level) -} - - -@Suppress("TooManyFunctions") -@OptIn(ExperimentalUnsignedTypes::class) -class CoreCryptoCentral private constructor(internal val cc: CoreCrypto, private val rootDir: String) { - suspend fun proteusClient(): ProteusClient = ProteusClientImpl(cc, rootDir) - - internal fun lower() = cc - - /** - * When you have a [ClientId], use this method to initialize your [MLSClient]. - * If you don't have a [ClientId], use [externallyGeneratedMlsClient] - * - * @param id client identifier - * @param ciphersuites for which a Basic Credential has to be initialized - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun mlsClient(id: ClientId, ciphersuites: Ciphersuites = Ciphersuites.DEFAULT): MLSClient { - return MLSClient(cc).apply { mlsInit(id, ciphersuites) } - } - - /** - * When you are relying on the DS to create a unique [ClientId] use this method. - * It will just initialize the crypto backend and return a handle to continue the initialization process later with - * [MLSClient.mlsInitWithClientId]. - * - * @param ciphersuites for which a Basic Credential has to be initialized - * @return a partially initialized [MLSClient] and a [ExternallyGeneratedHandle] to use in [MLSClient.mlsInitWithClientId] - */ - @Deprecated("Inside a transaction call CoreCryptoContext.mlsGenerateKeypairs() to get the handle. The CoreCryptoContext itself is a replacement for the MLSClient") - suspend fun externallyGeneratedMlsClient(ciphersuites: Ciphersuites = Ciphersuites.DEFAULT): Pair { - val client = MLSClient(cc) - val handle = client.mlsGenerateKeypairs(ciphersuites) - return client to handle - } - - /** - * Creates an enrollment instance with private key material you can use in order to fetch a new x509 certificate from the acme server. - * - * @param clientId client identifier e.g. `b7ac11a4-8f01-4527-af88-1c30885a7931:6add501bacd1d90e@example.com` - * @param displayName human-readable name displayed in the application e.g. `Smith, Alice M (QA)` - * @param handle user handle e.g. `alice.smith.qa@example.com` - * @param expirySec generated x509 certificate expiry - * @param ciphersuite for generating signing key material - * @param team name of the Wire team a user belongs to - * @return The new [E2EIEnrollment] enrollment to use with [e2eiMlsInitOnly] - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun e2eiNewEnrollment( - clientId: String, - displayName: String, - handle: String, - expirySec: UInt, - ciphersuite: Ciphersuite, - team: String? = null, - ): E2EIEnrollment { - return E2EIEnrollment(cc.e2eiNewEnrollment(clientId, displayName, handle, team, expirySec, ciphersuite.lower())) - } - - /** - * Generates an E2EI enrollment instance for a "regular" client (with a Basic credential) willing to migrate to E2EI. - * Once the enrollment is finished, use the instance in [e2eiRotateAll] to do the rotation. - * - * @param displayName human-readable name displayed in the application e.g. `Smith, Alice M (QA)` - * @param handle user handle e.g. `alice.smith.qa@example.com` - * @param expirySec generated x509 certificate expiry - * @param ciphersuite for generating signing key material - * @param team name of the Wire team a user belongs to - * @return The new [E2EIEnrollment] enrollment to use with [e2eiRotateAll] - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun e2eiNewActivationEnrollment( - displayName: String, - handle: String, - expirySec: UInt, - ciphersuite: Ciphersuite, - team: String? = null, - ): E2EIEnrollment { - return E2EIEnrollment( - cc.e2eiNewActivationEnrollment( - displayName, - handle, - team, - expirySec, - ciphersuite.lower() - ) - ) - } - - /** - * Generates an E2EI enrollment instance for a E2EI client (with a X509 certificate credential) having to change/rotate - * their credential, either because the former one is expired or it has been revoked. It lets you change the DisplayName - * or the handle if you need to. Once the enrollment is finished, use the instance in [e2eiRotateAll] to do the rotation. - * - * @param expirySec generated x509 certificate expiry - * @param ciphersuite for generating signing key material - * @param displayName human-readable name displayed in the application e.g. `Smith, Alice M (QA)` - * @param handle user handle e.g. `alice.smith.qa@example.com` - * @param team name of the Wire team a user belongs to - * @return The new [E2EIEnrollment] enrollment to use with [e2eiRotateAll] - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun e2eiNewRotateEnrollment( - expirySec: UInt, - ciphersuite: Ciphersuite, - displayName: String? = null, - handle: String? = null, - team: String? = null, - ): E2EIEnrollment { - return E2EIEnrollment( - cc.e2eiNewRotateEnrollment( - displayName, - handle, - team, - expirySec, - ciphersuite.lower() - ) - ) - } - - /** - * Use this method to initialize end-to-end identity when a client signs up and the grace period is already expired ; - * that means he cannot initialize with a Basic credential - * - * @param enrollment the enrollment instance used to fetch the certificates - * @param certificateChain the raw response from ACME server - * @param nbKeyPackage number of initial KeyPackage to create when initializing the client - * @return a [MLSClient] initialized with only a x509 credential - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun e2eiMlsInitOnly( - enrollment: E2EIEnrollment, - certificateChain: String, - nbKeyPackage: UInt? = DEFAULT_NB_KEY_PACKAGE - ): Pair { - val crlsDps = cc.e2eiMlsInitOnly(enrollment.lower(), certificateChain, nbKeyPackage) - return MLSClient(cc) to crlsDps?.toCrlDistributionPoint() - } - - /** - * Dumps the PKI environment as PEM - * - * @return a struct with different fields representing the PKI environment as PEM strings - */ - suspend fun e2eiDumpPKIEnv(): E2eiDumpedPkiEnv? { - return cc.e2eiDumpPkiEnv() - } - - /** - * Returns whether the E2EI PKI environment is setup (i.e. Root CA, Intermediates, CRLs) - */ - suspend fun e2eiIsPKIEnvSetup(): Boolean { - return cc.e2eiIsPkiEnvSetup() - } - - /** - * Registers a Root Trust Anchor CA for the use in E2EI processing. - * - * Please note that without a Root Trust Anchor, all validations *will* fail; - * So this is the first step to perform after initializing your E2EI client - * - * @param trustAnchorPEM - PEM certificate to anchor as a Trust Root - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun e2eiRegisterAcmeCA(trustAnchorPEM: String) { - return cc.e2eiRegisterAcmeCa(trustAnchorPEM) - } - - /** - * Registers an Intermediate CA for the use in E2EI processing. - * - * Please note that a Root Trust Anchor CA is needed to validate Intermediate CAs; - * You **need** to have a Root CA registered before calling this - * - * @param certPEM PEM certificate to register as an Intermediate CA - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun e2eiRegisterIntermediateCA(certPEM: String): CrlDistributionPoints? { - return cc.e2eiRegisterIntermediateCa(certPEM)?.toCrlDistributionPoint() - } - - /** - * Registers a CRL for the use in E2EI processing. - * - * Please note that a Root Trust Anchor CA is needed to validate CRLs; - * You **need** to have a Root CA registered before calling this - * - * @param crlDP CRL Distribution Point; Basically the URL you fetched it from - * @param crlDER DER representation of the CRL - * @return A [CrlRegistration] with the dirty state of the new CRL (see struct) and its expiration timestamp - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun e2eiRegisterCRL(crlDP: String, crlDER: ByteArray): CRLRegistration { - return cc.e2eiRegisterCrl(crlDP, crlDER).lift() - } - - /** - * Creates a commit in all local conversations for changing the credential. Requires first having enrolled a new X509 - * certificate with either [e2eiNewActivationEnrollment] or []e2eiNewRotateEnrollment] - * - * @param enrollment the enrollment instance used to fetch the certificates - * @param certificateChain the raw response from ACME server - * @param newKeyPackageCount number of KeyPackages with the new identity to create - * @return a [RotateBundle] with commits to fan-out to other group members, KeyPackages to upload and old ones to delete - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun e2eiRotateAll( - enrollment: E2EIEnrollment, - certificateChain: String, - newKeyPackageCount: UInt - ): RotateBundle { - return cc.e2eiRotateAll(enrollment.lower(), certificateChain, newKeyPackageCount).toRotateBundle() - } - - /** - * Allows persisting an active enrollment (for example while redirecting the user during OAuth) in order to resume - * it later with [e2eiEnrollmentStashPop] - * - * @param enrollment the enrollment instance to persist - * @return a handle to fetch the enrollment later with [e2eiEnrollmentStashPop] - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun e2eiEnrollmentStash(enrollment: E2EIEnrollment): EnrollmentHandle { - return cc.e2eiEnrollmentStash(enrollment.lower()).toUByteArray().asByteArray() - } - - /** - * Fetches the persisted enrollment and deletes it from the keystore - * - * @param handle returned by [e2eiEnrollmentStash] - * @returns the persisted enrollment instance - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun e2eiEnrollmentStashPop(handle: EnrollmentHandle): E2EIEnrollment { - return E2EIEnrollment(cc.e2eiEnrollmentStashPop(handle)) - } - - /** - * Closes this [CoreCryptoCentral] instance and deallocates all loaded resources. - * - * **CAUTION**: This {@link CoreCrypto} instance won't be usable after a call to this method, but there's no way to express this requirement in Kotlin, so you'll get errors instead! - */ - suspend fun close() { - cc.close() - } - - companion object { - private const val KEYSTORE_NAME = "keystore" - internal const val DEFAULT_NB_KEY_PACKAGE: UInt = 100U - - suspend operator fun invoke( - rootDir: String, - databaseKey: String, - ciphersuites: Ciphersuites = Ciphersuites.DEFAULT - ): CoreCryptoCentral { - val path = "$rootDir/$KEYSTORE_NAME" - File(rootDir).mkdirs() - val cc = coreCryptoDeferredInit(path, databaseKey) - cc.setCallbacks(Callbacks()) - return CoreCryptoCentral(cc, rootDir) - } - } -} - diff --git a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/CoreCryptoContext.kt b/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/CoreCryptoContext.kt index a2ed86dbcc..9c2b86f60a 100644 --- a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/CoreCryptoContext.kt +++ b/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/CoreCryptoContext.kt @@ -22,7 +22,7 @@ import com.wire.crypto.CoreCryptoContext import com.wire.crypto.CoreCryptoException import com.wire.crypto.CrlRegistration import com.wire.crypto.E2eiDumpedPkiEnv -import com.wire.crypto.client.CoreCryptoCentral.Companion.DEFAULT_NB_KEY_PACKAGE +import com.wire.crypto.client.CoreCrypto.Companion.DEFAULT_NB_KEY_PACKAGE import kotlin.time.Duration import kotlin.time.DurationUnit import kotlin.time.toDuration diff --git a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/E2eiClient.kt b/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/E2eiClient.kt index d1677454b7..3b5c92e203 100644 --- a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/E2eiClient.kt +++ b/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/E2eiClient.kt @@ -1,5 +1,8 @@ package com.wire.crypto.client +import com.wire.crypto.client.CoreCrypto +import com.wire.crypto.client.CoreCryptoContext + typealias JsonRawData = ByteArray data class AcmeDirectory(private val delegate: com.wire.crypto.AcmeDirectory) { @@ -162,8 +165,8 @@ class E2EIEnrollment(private val delegate: com.wire.crypto.E2eiEnrollment) { * @param challenge HTTP response body * @see https://www.rfc-editor.org/rfc/rfc8555.html#section-7.5.1 */ - @Deprecated("Use contextOidcChallengeResponse() with the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun oidcChallengeResponse(cc: CoreCryptoCentral, challenge: JsonRawData) = delegate.newOidcChallengeResponse(cc.lower(), challenge) + @Deprecated("Use contextOidcChallengeResponse() with the CoreCryptoContext object created from a CoreCrypto.transaction call") + suspend fun oidcChallengeResponse(cc: CoreCrypto, challenge: JsonRawData) = delegate.newOidcChallengeResponse(cc.lower(), challenge) /** * Parses the response from `POST /acme/{provisioner-name}/challenge/{challenge-id}` for OIDC challenge within a CoreCryptoContext. diff --git a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/MLSClient.kt b/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/MLSClient.kt deleted file mode 100644 index 8ace63f96c..0000000000 --- a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/MLSClient.kt +++ /dev/null @@ -1,518 +0,0 @@ -/* - * Wire - * Copyright (C) 2023 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -package com.wire.crypto.client - -import com.wire.crypto.client.CoreCryptoCentral.Companion.DEFAULT_NB_KEY_PACKAGE -import kotlin.time.Duration -import kotlin.time.DurationUnit -import kotlin.time.toDuration - -@Suppress("TooManyFunctions") -class MLSClient(private val cc: com.wire.crypto.CoreCrypto) { - - companion object { - private val keyRotationDuration: Duration = 30.toDuration(DurationUnit.DAYS) - private val defaultGroupConfiguration = com.wire.crypto.CustomConfiguration( - java.time.Duration.ofDays(keyRotationDuration.inWholeDays), - com.wire.crypto.MlsWirePolicy.PLAINTEXT - ) - } - - /** - * This is your entrypoint to initialize [com.wire.crypto.client.MLSClient] with a Basic Credential - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun mlsInit( - id: ClientId, - ciphersuites: Ciphersuites = Ciphersuites.DEFAULT, - nbKeyPackage: UInt? = DEFAULT_NB_KEY_PACKAGE - ) { - cc.mlsInit(id.lower(), ciphersuites.lower(), nbKeyPackage) - } - - /** - * Generates a MLS KeyPair/CredentialBundle with a temporary, random client ID. - * This method is designed to be used in conjunction with [mlsInitWithClientId] and represents the first step in this process - * - * @param ciphersuites - All the ciphersuites supported by this MLS client - * @return a list of random ClientId to use in [mlsInitWithClientId] - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun mlsGenerateKeypairs(ciphersuites: Ciphersuites = Ciphersuites.DEFAULT): ExternallyGeneratedHandle { - return cc.mlsGenerateKeypairs(ciphersuites.lower()).toExternallyGeneratedHandle() - } - - /** - * Updates the current temporary Client ID with the newly provided one. This is the second step in the externally-generated clients process. - * - * **Important:** This is designed to be called after [mlsGenerateKeypairs] - * - * @param clientId - The newly allocated Client ID from the MLS Authentication Service - * @param tmpClientIds - The random clientId you obtained in [mlsGenerateKeypairs], for authentication purposes - * @param ciphersuites - All the ciphersuites supported by this MLS client - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun mlsInitWithClientId( - clientId: ClientId, - tmpClientIds: ExternallyGeneratedHandle, - ciphersuites: Ciphersuites = Ciphersuites.DEFAULT - ) { - cc.mlsInitWithClientId(clientId.lower(), tmpClientIds.lower(), ciphersuites.lower()) - } - - /** - * Get the client's public signature key. To upload to the DS for further backend side validation - * - * @param ciphersuite of the signature key to get - * @return the client's public signature key - */ - suspend fun getPublicKey(ciphersuite: Ciphersuite = Ciphersuite.DEFAULT, credentialType: CredentialType = CredentialType.DEFAULT,): SignaturePublicKey { - return cc.clientPublicKey(ciphersuite.lower(), credentialType.lower()).toSignaturePublicKey() - } - - /** - * Generates the requested number of KeyPackages ON TOP of the existing ones e.g. if you already have created 100 - * KeyPackages (default value), requesting 10 will return the 10 oldest. Otherwise, if you request 200, 100 new will - * be generated. - * Unless explicitly deleted, KeyPackages are deleted upon [processWelcomeMessage] - * - * @param amount required amount - * @param ciphersuite of the KeyPackage to create - * @param credentialType of the KeyPackage to create - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun generateKeyPackages( - amount: UInt, - ciphersuite: Ciphersuite = Ciphersuite.DEFAULT, - credentialType: CredentialType = CredentialType.DEFAULT, - ): List { - return cc.clientKeypackages(ciphersuite.lower(), credentialType.lower(), amount).map { it.toMLSKeyPackage() } - } - - /** - * Number of unexpired KeyPackages currently in store - * - * @param ciphersuite of the KeyPackage to count - * @param credentialType of the KeyPackage to count - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun validKeyPackageCount( - ciphersuite: Ciphersuite = Ciphersuite.DEFAULT, - credentialType: CredentialType = CredentialType.DEFAULT - ): ULong { - return cc.clientValidKeypackagesCount(ciphersuite.lower(), credentialType.lower()) - } - - /** - * Prunes local KeyPackages after making sure they also have been deleted on the backend side. - * You should only use this after [CoreCryptoCentral.e2eiRotateAll] - * - * @param refs KeyPackage references from the [RotateBundle] - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun deleteKeyPackages(refs: List) { - // cannot be tested with the current API & helpers - return cc.deleteKeypackages(refs.map { it.lower() }) - } - - /** - * Checks if the Client is member of a given conversation and if the MLS Group is loaded up. - * - * @param id conversation identifier - */ - suspend fun conversationExists(id: MLSGroupId): Boolean = cc.conversationExists(id.lower()) - - /** - * Returns the current epoch of a conversation - * - * @param id conversation identifier - */ - suspend fun conversationEpoch(id: MLSGroupId): ULong = cc.conversationEpoch(id.lower()) - - /** - * Creates a new external Add proposal for self client to join a conversation. - * - * @param id conversation identifier - * @param epoch conversation epoch - * @param ciphersuite of the conversation to join - * @param ciphersuite to join the conversation with - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun joinConversation( - id: MLSGroupId, - epoch: ULong, - ciphersuite: Ciphersuite = Ciphersuite.DEFAULT, - credentialType: CredentialType = CredentialType.DEFAULT, - ): MlsMessage { - return cc.newExternalAddProposal(id.lower(), epoch, ciphersuite.lower(), credentialType.lower()).toMlsMessage() - } - - /** - * Allows to create an external commit to "apply" to join a group through its GroupInfo. - * - * If the DS accepts the external commit, you have to [mergePendingGroupFromExternalCommit] in order to get back - * a functional MLS group. On the opposite, if it rejects it, you can either retry by just calling again - * [joinByExternalCommit], no need to [clearPendingGroupFromExternalCommit]. If you want to abort the operation - * (too many retries or the user decided to abort), you can use [clearPendingGroupFromExternalCommit] in order not - * to bloat the user's storage but nothing bad can happen if you forget to except some storage space wasted. - * - * @param groupInfo a TLS encoded GroupInfo fetched from the Delivery Service - * @param credentialType to join the group with - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun joinByExternalCommit( - groupInfo: GroupInfo, - credentialType: CredentialType = CredentialType.DEFAULT, - configuration: com.wire.crypto.CustomConfiguration = defaultGroupConfiguration, - ): CommitBundle { - // cannot be tested since the groupInfo required is not wrapped in a MlsMessage whereas the one returned - // in Commit Bundles is... because that's the API the backend imposed - return cc.joinByExternalCommit(groupInfo.lower(), configuration, credentialType.lower()).lift() - } - - /** - * This merges the commit generated by [joinByExternalCommit], persists the group permanently - * and deletes the temporary one. This step makes the group operational and ready to encrypt/decrypt message. - * - * @param id conversation identifier - * @return eventually decrypted buffered messages if any - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun mergePendingGroupFromExternalCommit(id: MLSGroupId): List? { - return cc.mergePendingGroupFromExternalCommit(id.lower())?.map { it.lift() } - } - - /** - * In case the external commit generated by [joinByExternalCommit] is rejected by the Delivery Service, and we - * want to abort this external commit once for all, we can wipe out the pending group from the keystore in order - * not to waste space. - * - * @param id conversation identifier - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun clearPendingGroupFromExternalCommit(id: MLSGroupId) = cc.clearPendingGroupFromExternalCommit(id.lower()) - - /** - * Creates a new conversation with the current client being the sole member. - * You will want to use [addMember] afterward to add clients to this conversation. - * - * @param id conversation identifier - * @param ciphersuite of the conversation. A credential for the given ciphersuite must already have been created - * @param creatorCredentialType kind of credential the creator wants to create the group with - * @param externalSenders keys fetched from backend for validating external remove proposals - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun createConversation( - id: MLSGroupId, - ciphersuite: Ciphersuite = Ciphersuite.MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519, - creatorCredentialType: CredentialType = CredentialType.Basic, - externalSenders: List = emptyList(), - ) { - val cfg = com.wire.crypto.ConversationConfiguration( - ciphersuite.lower(), - externalSenders.map { it.lower() }, - defaultGroupConfiguration, - ) - - cc.createConversation(id.lower(), creatorCredentialType.lower(), cfg) - } - - /** - * Wipes and destroys the local storage of a given conversation / MLS group. - * - * @param id conversation identifier - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun wipeConversation(id: MLSGroupId) = cc.wipeConversation(id.lower()) - - /** - * Ingest a TLS-serialized MLS welcome message to join an existing MLS group. - * - * Important: you have to catch the error `OrphanWelcome`, ignore it and then try to join this group with an external commit. - * - * @param welcome - TLS-serialized MLS Welcome message - * @param configuration - configuration of the MLS group - * @return The conversation ID of the newly joined group. You can use the same ID to decrypt/encrypt messages - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun processWelcomeMessage( - welcome: Welcome, - configuration: com.wire.crypto.CustomConfiguration = defaultGroupConfiguration - ): WelcomeBundle { - return cc.processWelcomeMessage(welcome.lower(), configuration).lift() - } - - /** - * Encrypts a message for a given conversation. - * - * @param id conversation identifier - * @param message - The plaintext message to encrypt - * @return the encrypted payload for the given group. This needs to be fanned out to the other members of the group. - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun encryptMessage(id: MLSGroupId, message: PlaintextMessage): MlsMessage { - return cc.encryptMessage(id.lower(), message.lower()).toMlsMessage() - } - - /** - * Decrypts a message for a given conversation - * - * @param id conversation identifier - * @param message [MlsMessage] (either Application or Handshake message) from the DS - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun decryptMessage(id: MLSGroupId, message: MlsMessage): DecryptedMessage { - return cc.decryptMessage(id.lower(), message.lower()).lift() - } - - /** - * Adds new clients to a conversation, assuming the current client has the right to add new clients to the conversation. - * - * **CAUTION**: [commitAccepted] **HAS TO** be called afterward **ONLY IF** the Delivery Service responds'200 OK' to the [CommitBundle] upload. - * It will "merge" the commit locally i.e. increment the local group epoch, use new encryption secrets etc... - * - * @param id conversation identifier - * @param KeyPackages of the new clients to add - * @return a [CommitBundle] to upload to the backend and if it succeeds call [commitAccepted] - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun addMember(id: MLSGroupId, keyPackages: List): CommitBundle { - return cc.addClientsToConversation(id.lower(), keyPackages.map { it.lower() }).lift() - } - - /** - * Removes the provided clients from a conversation; Assuming those clients exist and the current client is allowed - * to do so, otherwise this operation does nothing. - * - * **CAUTION**: [commitAccepted] **HAS TO** be called afterward **ONLY IF** the Delivery Service responds'200 OK' to the [CommitBundle] upload. - * It will "merge" the commit locally i.e. increment the local group epoch, use new encryption secrets etc... - * - * @param id conversation identifier - * @param members client identifier to delete - * @return a [CommitBundle] to upload to the backend and if it succeeds call [commitAccepted] - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun removeMember(id: MLSGroupId, members: List): CommitBundle { - val clientIds = members.map { it.lower() } - return cc.removeClientsFromConversation(id.lower(), clientIds).lift() - } - - /** - * Creates an update commit which forces every client to update their LeafNode in the conversation. - * - * **CAUTION**: [commitAccepted] **HAS TO** be called afterward **ONLY IF** the Delivery Service responds'200 OK' to the [CommitBundle] upload. - * It will "merge" the commit locally i.e. increment the local group epoch, use new encryption secrets etc... - * - * @param id conversation identifier - * @return a [CommitBundle] to upload to the backend and if it succeeds call [commitAccepted] - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun updateKeyingMaterial(id: MLSGroupId) = cc.updateKeyingMaterial(id.lower()).lift() - - /** - * Creates an update commit which replaces your leaf containing basic credentials with a leaf node containing x509 credentials in the conversation. - * - * NOTE: you can only call this after you've completed the enrollment for an end-to-end identity, calling this without - * a valid end-to-end identity will result in an error. - * - * **CAUTION**: [commitAccepted] **HAS TO** be called afterward **ONLY IF** the Delivery Service responds'200 OK' to the [CommitBundle] upload. - * It will "merge" the commit locally i.e. increment the local group epoch, use new encryption secrets etc... - * - * @param id conversation identifier - * @return a [CommitBundle] to upload to the backend and if it succeeds call [commitAccepted] - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun e2eiRotate(id: MLSGroupId) = cc.e2eiRotate(id.lower()).lift() - - /** - * Commits the local pending proposals and returns the {@link CommitBundle} object containing what can result from this operation. - * - * *CAUTION**: [commitAccepted] **HAS TO** be called afterward **ONLY IF** the Delivery Service responds'200 OK' to the [CommitBundle] upload. - * It will "merge" the commit locally i.e. increment the local group epoch, use new encryption secrets etc... - * - * @param id conversation identifier - * @return a [CommitBundle] to upload to the backend and if it succeeds call [commitAccepted] - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun commitPendingProposals(id: MLSGroupId): CommitBundle? { - return cc.commitPendingProposals(id.lower())?.lift() - } - - /** - * Creates a new proposal for adding a client to the MLS group - * - * @param id conversation identifier - * @param keyPackage (TLS serialized) fetched from the DS - * @return a [ProposalBundle] which allows to roll back this proposal with [clearPendingProposal] in case the DS rejects it - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun newAddProposal(id: MLSGroupId, keyPackage: MLSKeyPackage): ProposalBundle { - return cc.newAddProposal(id.lower(), keyPackage.lower()).lift() - } - - /** - * Creates a new proposal for removing a client from the MLS group - * - * @param id conversation identifier - * @param clientId of the client to remove - * @return a [ProposalBundle] which allows to roll back this proposal with [clearPendingProposal] in case the DS rejects it - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun newRemoveProposal(id: MLSGroupId, clientId: ClientId): ProposalBundle { - return cc.newRemoveProposal(id.lower(), clientId.lower()).lift() - } - - /** - * Creates a new proposal to update the current client LeafNode key material within the MLS group - * - * @param id conversation identifier - * @return a [ProposalBundle] which allows to roll back this proposal with [clearPendingProposal] in case the DS rejects it - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun newUpdateProposal(id: MLSGroupId): ProposalBundle { - return cc.newUpdateProposal(id.lower()).lift() - } - - /** - * Allows to mark the latest commit produced as "accepted" and be able to safely merge it into the local group state - * - * @param id conversation identifier - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun commitAccepted(id: MLSGroupId): List? { - return cc.commitAccepted(id.lower())?.map { it.lift() } - } - - /** - * Allows to remove a pending proposal (rollback). Use this when backend rejects the proposal you just sent e.g. if permissions have changed meanwhile. - * - * **CAUTION**: only use this when you had an explicit response from the Delivery Service - * e.g. 403 or 409. Do not use otherwise e.g. 5xx responses, timeout etc… - * - * @param id conversation identifier - * @param proposalRef you get from a [ProposalBundle] - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun clearPendingProposal(id: MLSGroupId, proposalRef: ProposalRef) { - cc.clearPendingProposal(id.lower(), proposalRef.lower()) - } - - /** - * Allows to remove a pending commit (rollback). Use this when backend rejects the commit you just sent e.g. if permissions have changed meanwhile. - * - * **CAUTION**: only use this when you had an explicit response from the Delivery Service - * e.g. 403. Do not use otherwise e.g. 5xx responses, timeout etc... - * **DO NOT** use when Delivery Service responds 409, pending state will be renewed in [decryptMessage] - * - * @param id conversation identifier - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun clearPendingCommit(id: MLSGroupId) { - cc.clearPendingCommit(id.lower()) - } - - /** - * Returns all clients from group's members - * - * @param id conversation identifier - * @return All the clients from the members of the group - */ - suspend fun members(id: MLSGroupId): List { - return cc.getClientIds(id.lower()).map { it.toClientId() } - } - - /** - * Derives a new key from the group to use with AVS - * - * @param id conversation identifier - * @param keyLength the length of the key to be derived. If the value is higher than the bounds of `u16` or the context hash * 255, an error will be returned - */ - suspend fun deriveAvsSecret(id: MLSGroupId, keyLength: UInt): AvsSecret { - return cc.exportSecretKey(id.lower(), keyLength).toAvsSecret() - } - - /** - * Returns the raw public key of the single external sender present in this group. - * This should be used to initialize a subconversation - * - * @param id conversation identifier - * @param keyLength the length of the key to be derived. If the value is higher than the bounds of `u16` or the context hash * 255, an error will be returned - */ - suspend fun getExternalSender(id: MLSGroupId): ExternalSenderKey { - return cc.getExternalSender(id.lower()).toExternalSenderKey() - } - - /** - * Indicates when to mark a conversation as not verified i.e. when not all its members have a X509. - * Credential generated by Wire's end-to-end identity enrollment - * - * @param id conversation identifier - * @return the conversation state given current members - */ - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - suspend fun e2eiConversationState(id: MLSGroupId): com.wire.crypto.E2eiConversationState { - return cc.e2eiConversationState(id.lower()) - } - - /** - * Returns true when end-to-end-identity is enabled for the given Ciphersuite - * - * @param ciphersuite of the credential to check - * @returns true if end-to-end identity is enabled for the given ciphersuite - */ - suspend fun e2eiIsEnabled(ciphersuite: Ciphersuite = Ciphersuite.DEFAULT): Boolean { - return cc.e2eiIsEnabled(ciphersuite.lower()) - } - - /** - * From a given conversation, get the identity of the members supplied. Identity is only present for members with a - * Certificate Credential (after turning on end-to-end identity). - * - * @param id conversation identifier - * @param deviceIds identifiers of the devices - * @returns identities or if no member has a x509 certificate, it will return an empty List - */ - suspend fun getDeviceIdentities(id: MLSGroupId, deviceIds: List): List { - return cc.getDeviceIdentities(id.lower(), deviceIds.map { it.lower() }).map { it.lift() } - } - - /** - * From a given conversation, get the identity of the users (device holders) supplied. - * Identity is only present for devices with a Certificate Credential (after turning on end-to-end identity). - * If no member has a x509 certificate, it will return an empty Vec. - * - * @param id conversation identifier - * @param userIds user identifiers hyphenated UUIDv4 e.g. 'bd4c7053-1c5a-4020-9559-cd7bf7961954' - * @returns a Map with all the identities for a given users. Consumers are then recommended to reduce those identities to determine the actual status of a user. - */ - suspend fun getUserIdentities(id: MLSGroupId, userIds: List): Map> { - return cc.getUserIdentities(id.lower(), userIds).mapValues { (_, v) -> v.map { it.lift() } } - } - - /** - * Gets the e2ei conversation state from a `GroupInfo`. Useful to check if the group has e2ei - * turned on or not before joining it. - * - * @param groupInfo a TLS encoded GroupInfo fetched from the Delivery Service - * @param credentialType kind of Credential to check usage of. Defaults to X509 for now as no other value will give any result. - */ - suspend fun getCredentialInUse(groupInfo: GroupInfo, credentialType: CredentialType = CredentialType.X509): com.wire.crypto.E2eiConversationState { - return cc.getCredentialInUse(groupInfo.lower(), credentialType.lower()) - } -} diff --git a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/ProteusClient.kt b/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/ProteusClient.kt deleted file mode 100644 index 81382124a7..0000000000 --- a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/ProteusClient.kt +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Wire - * Copyright (C) 2023 Wire Swiss GmbH - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see http://www.gnu.org/licenses/. - */ - -package com.wire.crypto.client - -import com.wire.crypto.CoreCrypto -import com.wire.crypto.CoreCryptoException -import java.io.File - -typealias SessionId = String - -data class PreKey( - val id: UShort, - val data: ByteArray -) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as PreKey - - if (id != other.id) return false - if (!data.contentEquals(other.data)) return false - - return true - } - - override fun hashCode(): Int { - var result = id.hashCode() - result = 31 * result + data.contentHashCode() - return result - } -} - -interface ProteusClient { - - suspend fun getIdentity(): ByteArray - - suspend fun getLocalFingerprint(): ByteArray - - suspend fun getRemoteFingerprint(sessionId: SessionId): ByteArray - - suspend fun newPreKeys(from: Int, count: Int): ArrayList - - suspend fun newLastPreKey(): PreKey - - suspend fun doesSessionExist(sessionId: SessionId): Boolean - - suspend fun createSession(preKeyCrypto: PreKey, sessionId: SessionId) - - suspend fun deleteSession(sessionId: SessionId) - - suspend fun decrypt(message: ByteArray, sessionId: SessionId): ByteArray - - suspend fun encrypt(message: ByteArray, sessionId: SessionId): ByteArray - - suspend fun encryptBatched(message: ByteArray, sessionIds: List): Map - - suspend fun encryptWithPreKey( - message: ByteArray, - preKey: PreKey, - sessionId: SessionId - ): ByteArray -} - -@Suppress("TooManyFunctions") -class ProteusClientImpl private constructor(private val coreCrypto: CoreCrypto): ProteusClient { - override suspend fun getIdentity(): ByteArray { - return ByteArray(0) - } - - override suspend fun getLocalFingerprint(): ByteArray { - return wrapException { coreCrypto.proteusFingerprint().toByteArray() } - } - - override suspend fun getRemoteFingerprint(sessionId: SessionId): ByteArray { - return wrapException { coreCrypto.proteusFingerprintRemote(sessionId).toByteArray() } - } - - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - override suspend fun newPreKeys(from: Int, count: Int): ArrayList { - return wrapException { - from.until(from + count).map { - toPreKey(it.toUShort(), coreCrypto.proteusNewPrekey(it.toUShort())) - } as ArrayList - } - } - - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - override suspend fun newLastPreKey(): PreKey { - return wrapException { toPreKey(coreCrypto.proteusLastResortPrekeyId(), coreCrypto.proteusLastResortPrekey()) } - } - - override suspend fun doesSessionExist(sessionId: SessionId): Boolean { - return wrapException { - coreCrypto.proteusSessionExists(sessionId) - } - } - - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - override suspend fun createSession(preKeyCrypto: PreKey, sessionId: SessionId) { - wrapException { coreCrypto.proteusSessionFromPrekey(sessionId, preKeyCrypto.data) } - } - - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - override suspend fun deleteSession(sessionId: SessionId) { - wrapException { - coreCrypto.proteusSessionDelete(sessionId) - } - } - - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - override suspend fun decrypt(message: ByteArray, sessionId: SessionId): ByteArray { - val sessionExists = doesSessionExist(sessionId) - - return wrapException { - if (sessionExists) { - val decryptedMessage = coreCrypto.proteusDecrypt(sessionId, message) - coreCrypto.proteusSessionSave(sessionId) - decryptedMessage - } else { - val decryptedMessage = coreCrypto.proteusSessionFromMessage(sessionId, message) - coreCrypto.proteusSessionSave(sessionId) - decryptedMessage - } - } - } - - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - override suspend fun encrypt(message: ByteArray, sessionId: SessionId): ByteArray { - return wrapException { - val encryptedMessage = coreCrypto.proteusEncrypt(sessionId, message) - coreCrypto.proteusSessionSave(sessionId) - encryptedMessage - } - } - - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - override suspend fun encryptBatched(message: ByteArray, sessionIds: List): Map { - return wrapException { - coreCrypto.proteusEncryptBatched(sessionIds.map { it }, message).mapNotNull { entry -> - entry.key to entry.value - } - }.toMap() - } - - @Deprecated("Use this method from the CoreCryptoContext object created from a CoreCryptoCentral.transaction call") - override suspend fun encryptWithPreKey( - message: ByteArray, - preKey: PreKey, - sessionId: SessionId - ): ByteArray { - return wrapException { - coreCrypto.proteusSessionFromPrekey(sessionId, preKey.data) - val encryptedMessage = coreCrypto.proteusEncrypt(sessionId, message) - coreCrypto.proteusSessionSave(sessionId) - encryptedMessage - } - } - - @Suppress("TooGenericExceptionCaught") - private suspend fun wrapException(b: suspend () -> T): T { - try { - return b() - } catch (e: CoreCryptoException) { - throw ProteusException(e.message, ProteusException.fromProteusCode(coreCrypto.proteusLastErrorCode().toInt()), e.cause) - } catch (e: Exception) { - throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, e.cause) - } - } - - @OptIn(ExperimentalUnsignedTypes::class) - companion object { - private fun toUByteList(value: ByteArray): List = value.asUByteArray().asList() - private fun toByteArray(value: List) = value.toUByteArray().asByteArray() - private fun toPreKey(id: UShort, data: ByteArray): PreKey = - PreKey(id, data) - - public fun needsMigration(rootDir: File): Boolean { - return cryptoBoxFilesExists(rootDir) - } - - private fun cryptoBoxFilesExists(rootDir: File): Boolean = - CRYPTO_BOX_FILES.any { - rootDir.resolve(it).exists() - } - - private val CRYPTO_BOX_FILES = listOf("identities", "prekeys", "sessions", "version") - - private fun deleteCryptoBoxFiles(rootDir: String): Boolean = - CRYPTO_BOX_FILES.fold(true) { acc, file -> - acc && File(rootDir).resolve(file).deleteRecursively() - } - - private suspend fun migrateFromCryptoBoxIfNecessary(coreCrypto: CoreCrypto, rootDir: String) { - if (cryptoBoxFilesExists(File(rootDir))) { - coreCrypto.proteusCryptoboxMigrate(rootDir) - deleteCryptoBoxFiles(rootDir) - } - } - - suspend operator fun invoke(coreCrypto: CoreCrypto, rootDir: String): ProteusClientImpl { - try { - migrateFromCryptoBoxIfNecessary(coreCrypto, rootDir) - coreCrypto.proteusInit() - return ProteusClientImpl(coreCrypto) - } catch (e: CoreCryptoException) { - throw ProteusException(e.message, ProteusException.fromProteusCode(coreCrypto.proteusLastErrorCode().toInt()), e.cause) - } catch (e: Exception) { - throw ProteusException(e.message, ProteusException.Code.UNKNOWN_ERROR, e.cause) - } - } - } -} diff --git a/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/ProteusModel.kt b/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/ProteusModel.kt new file mode 100644 index 0000000000..99b5e877a8 --- /dev/null +++ b/crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/client/ProteusModel.kt @@ -0,0 +1,44 @@ +/* + * Wire + * Copyright (C) 2023 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +package com.wire.crypto.client + +typealias SessionId = String + +data class PreKey( + val id: UShort, + val data: ByteArray +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as PreKey + + if (id != other.id) return false + if (!data.contentEquals(other.data)) return false + + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + data.contentHashCode() + return result + } +} diff --git a/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/client/E2EITest.kt b/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/client/E2EITest.kt index af134e87a3..6b1b2f0aa8 100644 --- a/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/client/E2EITest.kt +++ b/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/client/E2EITest.kt @@ -33,14 +33,14 @@ internal class E2EITest { fun sample_e2ei_enrollment_should_succeed() = runTest { val root = Files.createTempDirectory("mls").toFile() val keyStore = root.resolve("keystore-$aliceId") - val cc = CoreCryptoCentral(keyStore.absolutePath, "secret") - val enrollment = cc.e2eiNewEnrollment( + val cc = CoreCrypto(keyStore.absolutePath, "secret") + val enrollment = cc.transaction { it.e2eiNewEnrollment( clientId = "b7ac11a4-8f01-4527-af88-1c30885a7931:6c1866f567616f31@wire.com", displayName = "Alice Smith", handle = "alice_wire", expirySec = (90 * 24 * 3600).toUInt(), ciphersuite = Ciphersuite.DEFAULT - ) + )} val directoryResponse = """{ "newNonce": "https://example.com/acme/new-nonce", "newAccount": "https://example.com/acme/new-account", @@ -200,20 +200,20 @@ internal class E2EITest { fun conversation_should_be_not_verified_when_at_least_1_of_the_members_uses_a_Basic_credential() = runTest { val (alice, bob) = newClients(aliceId, bobId) - bob.createConversation(id) + bob.transaction { it.createConversation(id) } - val aliceKp = alice.generateKeyPackages(1U, Ciphersuite.DEFAULT, CredentialType.DEFAULT).first() - val welcome = bob.addMember(id, listOf(aliceKp)).welcome!! - bob.commitAccepted(id) - val groupId = alice.processWelcomeMessage(welcome).id + val aliceKp = alice.transaction { it.generateKeyPackages(1U, Ciphersuite.DEFAULT, CredentialType.DEFAULT).first() } + val welcome = bob.transaction { it.addMember(id, listOf(aliceKp)).welcome!! } + bob.transaction { it.commitAccepted(id) } + val groupId = alice.transaction { it.processWelcomeMessage(welcome).id } - assertThat(alice.e2eiConversationState(groupId)).isEqualTo(E2eiConversationState.NOT_ENABLED) - assertThat(bob.e2eiConversationState(groupId)).isEqualTo(E2eiConversationState.NOT_ENABLED) + assertThat(alice.transaction { it.e2eiConversationState(groupId) }).isEqualTo(E2eiConversationState.NOT_ENABLED) + assertThat(bob.transaction { it.e2eiConversationState(groupId) }).isEqualTo(E2eiConversationState.NOT_ENABLED) } @Test fun e2ei_should_not_be_enabled_for_a_Basic_Credential() = runTest { val (alice) = newClients(aliceId) - assertThat(alice.e2eiIsEnabled()).isFalse() + assertThat(alice.transaction { it.e2eiIsEnabled()}).isFalse() } } diff --git a/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/client/MLSTest.kt b/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/client/MLSTest.kt index 2a9468916b..42dc36c1d3 100644 --- a/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/client/MLSTest.kt +++ b/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/client/MLSTest.kt @@ -19,6 +19,7 @@ package com.wire.crypto.client import com.wire.crypto.CoreCryptoException +import com.wire.crypto.coreCryptoDeferredInit import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.assertj.core.api.AssertionsForInterfaceTypes.assertThat @@ -59,8 +60,9 @@ class MLSTest { @Test fun externally_generated_ClientId_should_init_the_MLS_client() = runTest { - val (alice, handle) = initCc().externallyGeneratedMlsClient() - alice.mlsInitWithClientId(aliceId.toClientId(), handle) + val alice = initCc() + val handle = alice.transaction { it.mlsGenerateKeypairs() } + alice.transaction { it.mlsInitWithClientId(aliceId.toClientId(), handle) } } @Test @@ -121,15 +123,15 @@ class MLSTest { @Test fun getPublicKey_should_return_non_empty_result() = runTest { val (alice) = newClients(aliceId) - assertThat(alice.getPublicKey(Ciphersuite.DEFAULT).value).isNotEmpty() + assertThat(alice.transaction { it.getPublicKey(Ciphersuite.DEFAULT).value }).isNotEmpty() } @Test fun conversationExists_should_return_true() = runTest { val (alice) = newClients(aliceId) - assertThat(alice.conversationExists(id)).isFalse() - alice.createConversation(id) - assertThat(alice.conversationExists(id)).isTrue() + assertThat(alice.transaction { it.conversationExists(id) }).isFalse() + alice.transaction { it.createConversation(id) } + assertThat(alice.transaction { it.conversationExists(id) }).isTrue() } @Test @@ -137,34 +139,32 @@ class MLSTest { val (alice) = newClients(aliceId) // by default - assertThat(alice.validKeyPackageCount()).isEqualTo(100.toULong()) - - assertThat(alice.generateKeyPackages(200U)).isNotEmpty().hasSize(200) - - assertThat(alice.validKeyPackageCount()).isEqualTo(200.toULong()) + assertThat(alice.transaction { it.validKeyPackageCount() }).isEqualTo(100.toULong()) + assertThat(alice.transaction { it.generateKeyPackages(200U) }).isNotEmpty().hasSize(200) + assertThat(alice.transaction { it.validKeyPackageCount() }).isEqualTo(200.toULong()) } @Test fun given_new_conversation_when_calling_conversationEpoch_should_return_epoch_0() = runTest { val (alice) = newClients(aliceId) - alice.createConversation(id) - assertThat(alice.conversationEpoch(id)).isEqualTo(0UL) + alice.transaction { it.createConversation(id) } + assertThat(alice.transaction { it.conversationEpoch(id) }).isEqualTo(0UL) } @Test fun updateKeyingMaterial_should_process_the_commit_message() = runTest { val (alice, bob) = newClients(aliceId, bobId) - bob.createConversation(id) + bob.transaction { it.createConversation(id) } - val aliceKp = alice.generateKeyPackages(1U).first() - val welcome = bob.addMember(id, listOf(aliceKp)).welcome!! - bob.commitAccepted(id) - val groupId = alice.processWelcomeMessage(welcome).id + val aliceKp = alice.transaction { it.generateKeyPackages(1U).first() } + val welcome = bob.transaction { it.addMember(id, listOf(aliceKp)).welcome!! } + bob.transaction { it.commitAccepted(id) } - val commit = bob.updateKeyingMaterial(id).commit + val groupId = alice.transaction { it.processWelcomeMessage(welcome).id } + val commit = bob.transaction { it.updateKeyingMaterial(id).commit } - val decrypted = alice.decryptMessage(groupId, commit) + val decrypted = alice.transaction { it.decryptMessage(groupId, commit) } assertThat(decrypted.message).isNull() assertThat(decrypted.commitDelay).isNull() assertThat(decrypted.senderClientId).isNull() @@ -175,11 +175,11 @@ class MLSTest { fun addMember_should_allow_joining_a_conversation_with_a_Welcome() = runTest { val (alice, bob) = newClients(aliceId, bobId) - bob.createConversation(id) + bob.transaction { it.createConversation(id) } - val aliceKp = alice.generateKeyPackages(1U).first() - val welcome = bob.addMember(id, listOf(aliceKp)).welcome!! - val groupId = alice.processWelcomeMessage(welcome) + val aliceKp = alice.transaction { it.generateKeyPackages(1U).first() } + val welcome = bob.transaction { it.addMember(id, listOf(aliceKp)).welcome!! } + val groupId = alice.transaction { it.processWelcomeMessage(welcome) } // FIXME: simplify when https://youtrack.jetbrains.com/issue/KT-24874 fixed assertThat(groupId.id.toString()).isEqualTo(id.value.toHex()) @@ -189,17 +189,20 @@ class MLSTest { fun joinConversation_should_generate_an_Add_proposal() = runTest { val (alice1, alice2, bob) = newClients(aliceId, aliceId2, bobId) - bob.createConversation(id) + bob.transaction { it.createConversation(id) } - val alice1Kp = alice1.generateKeyPackages(1U).first() - bob.addMember(id, listOf(alice1Kp)) - bob.commitAccepted(id) + val alice1Kp = alice1.transaction { it.generateKeyPackages(1U).first() } + bob.transaction { + it.addMember(id, listOf(alice1Kp)) + it.commitAccepted(id) + Unit + } - val proposal = alice2.joinConversation(id, 1UL, Ciphersuite.DEFAULT, CredentialType.DEFAULT) - bob.decryptMessage(id, proposal) - val welcome = bob.commitPendingProposals(id)?.welcome!! - bob.commitAccepted(id) - val groupId = alice2.processWelcomeMessage(welcome) + val proposal = alice2.transaction { it.joinConversation(id, 1UL, Ciphersuite.DEFAULT, CredentialType.DEFAULT) } + bob.transaction { it.decryptMessage(id, proposal) } + val welcome = bob.transaction { it.commitPendingProposals(id)?.welcome!! } + bob.transaction { it.commitAccepted(id) } + val groupId = alice2.transaction { it.processWelcomeMessage(welcome) } // FIXME: simplify when https://youtrack.jetbrains.com/issue/KT-24874 fixed assertThat(groupId.id.toString()).isEqualTo(id.value.toHex()) @@ -209,18 +212,18 @@ class MLSTest { fun encryptMessage_should_encrypt_then_receiver_should_decrypt() = runTest { val (alice, bob) = newClients(aliceId, bobId) - bob.createConversation(id) + bob.transaction { it.createConversation(id) } - val aliceKp = alice.generateKeyPackages(1U).first() - val welcome = bob.addMember(id, listOf(aliceKp)).welcome!! - bob.commitAccepted(id) - val groupId = alice.processWelcomeMessage(welcome).id + val aliceKp = alice.transaction { it.generateKeyPackages(1U).first() } + val welcome = bob.transaction { it.addMember(id, listOf(aliceKp)).welcome!! } + bob.transaction { it.commitAccepted(id) } + val groupId = alice.transaction { it.processWelcomeMessage(welcome).id } val msg = "Hello World !" - val ciphertextMsg = alice.encryptMessage(groupId, msg.toPlaintextMessage()) + val ciphertextMsg = alice.transaction { it.encryptMessage(groupId, msg.toPlaintextMessage()) } assertThat(ciphertextMsg).isNotEqualTo(msg) - val plaintextMsg = bob.decryptMessage(groupId, ciphertextMsg).message!! + val plaintextMsg = bob.transaction { it.decryptMessage(groupId, ciphertextMsg).message!! } assertThat(String(plaintextMsg)).isNotEmpty().isEqualTo(msg) } @@ -228,33 +231,34 @@ class MLSTest { fun addMember_should_add_members_to_the_MLS_group() = runTest { val (alice, bob, carol) = newClients(aliceId, bobId, carolId) - bob.createConversation(id) - val aliceKp = alice.generateKeyPackages(1U).first() - val welcome = bob.addMember(id, listOf(aliceKp)).welcome!! - bob.commitAccepted(id) + bob.transaction { it.createConversation(id) } + val aliceKp = alice.transaction { it.generateKeyPackages(1U).first() } + val welcome = bob.transaction { it.addMember(id, listOf(aliceKp)).welcome!! } + bob.transaction { it.commitAccepted(id) } - alice.processWelcomeMessage(welcome) + alice.transaction { it.processWelcomeMessage(welcome) } - val carolKp = carol.generateKeyPackages(1U).first() - val commit = bob.addMember(id, listOf(carolKp)).commit + val carolKp = carol.transaction { it.generateKeyPackages(1U).first() } + val commit = bob.transaction { it.addMember(id, listOf(carolKp)).commit } - val decrypted = alice.decryptMessage(id, commit) + val decrypted = alice.transaction { it.decryptMessage(id, commit) } assertThat(decrypted.message).isNull() - assertThat(alice.members(id).containsAll(listOf(aliceId, bobId, carolId).map { it.toClientId() })) + val members = alice.transaction { it.members(id) } + assertThat(members.containsAll(listOf(aliceId, bobId, carolId).map { it.toClientId() })) } @Test fun addMember_should_return_a_valid_Welcome_message() = runTest { val (alice, bob) = newClients(aliceId, bobId) - bob.createConversation(id) + bob.transaction { it.createConversation(id) } - val aliceKp = alice.generateKeyPackages(1U).first() - val welcome = bob.addMember(id, listOf(aliceKp)).welcome!! - bob.commitAccepted((id)) + val aliceKp = alice.transaction { it.generateKeyPackages(1U).first() } + val welcome = bob.transaction { it.addMember(id, listOf(aliceKp)).welcome!! } + bob.transaction { it.commitAccepted((id)) } - val groupId = alice.processWelcomeMessage(welcome) + val groupId = alice.transaction { it.processWelcomeMessage(welcome) } // FIXME: simplify when https://youtrack.jetbrains.com/issue/KT-24874 fixed assertThat(groupId.id.toString()).isEqualTo(id.value.toHex()) } @@ -263,18 +267,18 @@ class MLSTest { fun removeMember_should_remove_members_from_the_MLS_group() = runTest { val (alice, bob, carol) = newClients(aliceId, bobId, carolId) - bob.createConversation(id) + bob.transaction { it.createConversation(id) } - val aliceKp = alice.generateKeyPackages(1U).first() - val carolKp = carol.generateKeyPackages(1U).first() - val welcome = bob.addMember(id, listOf(aliceKp, carolKp)).welcome!! - bob.commitAccepted(id) - val conversationId = alice.processWelcomeMessage(welcome).id + val aliceKp = alice.transaction { it.generateKeyPackages(1U).first() } + val carolKp = carol.transaction { it.generateKeyPackages(1U).first() } + val welcome = bob.transaction { it.addMember(id, listOf(aliceKp, carolKp)).welcome!! } + bob.transaction { it.commitAccepted(id) } + val conversationId = alice.transaction { it.processWelcomeMessage(welcome).id } val carolMember = listOf(carolId.toClientId()) - val commit = bob.removeMember(conversationId, carolMember).commit + val commit = bob.transaction { it.removeMember(conversationId, carolMember).commit } - val decrypted = alice.decryptMessage(conversationId, commit) + val decrypted = alice.transaction { it.decryptMessage(conversationId, commit) } assertThat(decrypted.message).isNull() } @@ -282,60 +286,60 @@ class MLSTest { fun creating_proposals_and_removing_them() = runTest { val (alice, bob, carol) = newClients(aliceId, bobId, carolId) - alice.createConversation(id) + alice.transaction { it.createConversation(id) } - val bobKp = bob.generateKeyPackages(1U).first() + val bobKp = bob.transaction { it.generateKeyPackages(1U).first() } // Add proposal - alice.newAddProposal(id, bobKp) - val welcome = alice.commitPendingProposals(id)!!.welcome!! - alice.commitAccepted(id) + alice.transaction { it.newAddProposal(id, bobKp) } + val welcome = alice.transaction { it.commitPendingProposals(id)!!.welcome!! } + alice.transaction { it.commitAccepted(id) } - bob.processWelcomeMessage(welcome) + bob.transaction { it.processWelcomeMessage(welcome) } // Now creating & clearing proposal - val carolKp = carol.generateKeyPackages(1U).first() - val addProposal = alice.newAddProposal(id, carolKp) - val removeProposal = alice.newRemoveProposal(id, bobId.toClientId()) - val updateProposal = alice.newUpdateProposal(id) + val carolKp = carol.transaction { it.generateKeyPackages(1U).first() } + val addProposal = alice.transaction { it.newAddProposal(id, carolKp) } + val removeProposal = alice.transaction { it.newRemoveProposal(id, bobId.toClientId()) } + val updateProposal = alice.transaction { it.newUpdateProposal(id) } val proposals = listOf(addProposal, removeProposal, updateProposal) - proposals.forEach { - alice.clearPendingProposal(id, it.proposalRef) + proposals.forEach { proposal -> + alice.transaction { it.clearPendingProposal(id, proposal.proposalRef) } } // should be null since we cleared all proposals - assertThat(alice.commitPendingProposals(id)).isNull() + assertThat(alice.transaction { it.commitPendingProposals(id) }).isNull() } @Test fun clearPendingCommit_should_clear_the_pending_commit() = runTest { val (alice) = newClients(aliceId) - alice.createConversation(id) - - alice.updateKeyingMaterial(id) - alice.clearPendingCommit(id) + alice.transaction { + it.createConversation(id) + it.updateKeyingMaterial(id) + it.clearPendingCommit(id) + } // encrypting a message would have failed if there was a pending commit - assertThat(alice.encryptMessage(id, "Hello".toPlaintextMessage())) + assertThat(alice.transaction { it.encryptMessage(id, "Hello".toPlaintextMessage()) }) } @Test fun wipeConversation_should_delete_the_conversation_from_the_keystore() = runTest { val (alice) = newClients(aliceId) - alice.createConversation(id) + alice.transaction { it.createConversation(id) } assertThatNoException().isThrownBy { - runBlocking { alice.wipeConversation(id) } + runBlocking { alice.transaction { it.wipeConversation(id) } } } } @Test fun deriveAvsSecret_should_generate_a_secret_with_the_right_length() = runTest { val (alice) = newClients(aliceId) - alice.createConversation(id) - + alice.transaction { it.createConversation(id) } val n = 50 val secrets = (0 until n).map { - val secret = alice.deriveAvsSecret(id, 32U) + val secret = alice.transaction { it.deriveAvsSecret(id, 32U) } assertThat(secret.value).hasSize(32) secret }.toSet() @@ -344,13 +348,17 @@ class MLSTest { } fun newClients(vararg clientIds: String) = runBlocking { - clientIds.map { initCc().mlsClient(it.toClientId()) } + clientIds.map { clientID -> + val cc = initCc() + cc.transaction { it.mlsInit(clientID.toClientId()) } + cc + } } -fun initCc(): CoreCryptoCentral = runBlocking { +fun initCc(): CoreCrypto = runBlocking { val root = Files.createTempDirectory("mls").toFile() val keyStore = root.resolve("keystore-${randomIdentifier()}") - CoreCryptoCentral(keyStore.absolutePath, "secret") + CoreCrypto(keyStore.absolutePath, "secret") } fun randomIdentifier(n: Int = 12): String { diff --git a/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/client/ProteusClientTest.kt b/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/client/ProteusClientTest.kt index 48ad90e8c2..b17487e846 100644 --- a/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/client/ProteusClientTest.kt +++ b/crypto-ffi/bindings/jvm/src/test/kotlin/com/wire/crypto/client/ProteusClientTest.kt @@ -17,13 +17,12 @@ */ import com.wire.crypto.client.* -import kotlinx.coroutines.ExperimentalCoroutinesApi + import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import java.nio.file.Files import kotlin.test.* -@OptIn(ExperimentalCoroutinesApi::class) internal class ProteusClientTest { companion object { @@ -33,23 +32,25 @@ internal class ProteusClientTest { private const val bobSessionId = "bob1_session1" } - private suspend fun newProteusClient(clientId: ClientId): ProteusClient = runBlocking { + private fun newProteusClient(clientId: ClientId): CoreCrypto = runBlocking { val root = Files.createTempDirectory("mls").toFile() val keyStore = root.resolve("keystore-$clientId") - CoreCryptoCentral(keyStore.absolutePath, "secret").proteusClient() + val cc = CoreCrypto(keyStore.absolutePath, "secret") + cc.proteusInit() + cc } @Test fun givenProteusClient_whenCallingNewLastKey_thenItReturnsALastPreKey() = runTest { val aliceClient = newProteusClient(alice) - val lastPreKey = aliceClient.newLastPreKey() + val lastPreKey = aliceClient.transaction { it.proteusNewLastPreKey() } assertEquals(65535u, lastPreKey.id) } @Test fun givenProteusClient_whenCallingNewPreKeys_thenItReturnsAListOfPreKeys() = runTest { val aliceClient = newProteusClient(alice) - val preKeyList = aliceClient.newPreKeys(0, 10) + val preKeyList = aliceClient.transaction { it.proteusNewPreKeys(0, 10) } assertEquals(preKeyList.size, 10) } @@ -59,9 +60,9 @@ internal class ProteusClientTest { val bobClient = newProteusClient(bob) val message = "Hi Alice!" - val aliceKey = aliceClient.newPreKeys(0, 10).first() - val encryptedMessage = bobClient.encryptWithPreKey(message.encodeToByteArray(), aliceKey, aliceSessionId) - val decryptedMessage = aliceClient.decrypt(encryptedMessage, bobSessionId) + val aliceKey = aliceClient.transaction { it.proteusNewPreKeys(0, 10).first() } + val encryptedMessage = bobClient.transaction { it.proteusEncryptWithPreKey(message.encodeToByteArray(), aliceKey, aliceSessionId) } + val decryptedMessage = aliceClient.transaction { it.proteusDecrypt(encryptedMessage, bobSessionId) } assertEquals(message, decryptedMessage.decodeToString()) } @@ -69,14 +70,14 @@ internal class ProteusClientTest { fun givenSessionAlreadyExists_whenCallingDecrypt_thenMessageIsDecrypted() = runTest { val aliceClient = newProteusClient(alice) val bobClient = newProteusClient(bob) - val aliceKey = aliceClient.newPreKeys(0, 10).first() + val aliceKey = aliceClient.transaction { it.proteusNewPreKeys(0, 10).first() } val message1 = "Hi Alice!" - val encryptedMessage1 = bobClient.encryptWithPreKey(message1.encodeToByteArray(), aliceKey, aliceSessionId) - aliceClient.decrypt(encryptedMessage1, bobSessionId) + val encryptedMessage1 = bobClient.transaction { it.proteusEncryptWithPreKey(message1.encodeToByteArray(), aliceKey, aliceSessionId) } + aliceClient.transaction { it.proteusDecrypt(encryptedMessage1, bobSessionId) } val message2 = "Hi again Alice!" - val encryptedMessage2 = bobClient.encrypt(message2.encodeToByteArray(), aliceSessionId) - val decryptedMessage2 = aliceClient.decrypt(encryptedMessage2, bobSessionId) + val encryptedMessage2 = bobClient.transaction { it.proteusEncrypt(message2.encodeToByteArray(), aliceSessionId) } + val decryptedMessage2 = aliceClient.transaction { it.proteusDecrypt(encryptedMessage2, bobSessionId) } assertEquals(message2, decryptedMessage2.decodeToString()) } @@ -85,13 +86,13 @@ internal class ProteusClientTest { fun givenReceivingSameMessageTwice_whenCallingDecrypt_thenDuplicateMessageError() = runTest { val aliceClient = newProteusClient(alice) val bobClient = newProteusClient(bob) - val aliceKey = aliceClient.newPreKeys(0, 10).first() + val aliceKey = aliceClient.transaction { it.proteusNewPreKeys(0, 10).first() } val message1 = "Hi Alice!" - val encryptedMessage1 = bobClient.encryptWithPreKey(message1.encodeToByteArray(), aliceKey, aliceSessionId) - aliceClient.decrypt(encryptedMessage1, bobSessionId) + val encryptedMessage1 = bobClient.transaction { it.proteusEncryptWithPreKey(message1.encodeToByteArray(), aliceKey, aliceSessionId) } + aliceClient.transaction { it.proteusDecrypt(encryptedMessage1, bobSessionId) } val exception: ProteusException = assertFailsWith { - aliceClient.decrypt(encryptedMessage1, bobSessionId) + aliceClient.transaction { it.proteusDecrypt(encryptedMessage1, bobSessionId) } } assertEquals(ProteusException.Code.DUPLICATE_MESSAGE, exception.code) } @@ -100,13 +101,13 @@ internal class ProteusClientTest { fun givenMissingSession_whenCallingEncryptBatched_thenMissingSessionAreIgnored() = runTest { val aliceClient = newProteusClient(alice) val bobClient = newProteusClient(bob) - val aliceKey = aliceClient.newPreKeys(0, 10).first() + val aliceKey = aliceClient.transaction { it.proteusNewPreKeys(0, 10).first() } val message1 = "Hi Alice!" - bobClient.createSession(aliceKey, aliceSessionId) + bobClient.transaction { it.proteusCreateSession(aliceKey, aliceSessionId) } val missingAliceSessionId = "missing_session" val encryptedMessages = - bobClient.encryptBatched(message1.encodeToByteArray(), listOf(aliceSessionId, missingAliceSessionId)) + bobClient.transaction { it.proteusEncryptBatched(listOf(aliceSessionId, missingAliceSessionId), message1.encodeToByteArray()) } assertEquals(1, encryptedMessages.size) assertTrue(encryptedMessages.containsKey(aliceSessionId)) @@ -117,8 +118,8 @@ internal class ProteusClientTest { val aliceClient = newProteusClient(alice) val bobClient = newProteusClient(bob) - val aliceKey = aliceClient.newPreKeys(0, 10).first() - bobClient.createSession(aliceKey, aliceSessionId) - assertNotNull(bobClient.encrypt("Hello World".encodeToByteArray(), aliceSessionId)) + val aliceKey = aliceClient.transaction { it.proteusNewPreKeys(0, 10).first() } + bobClient.transaction { it.proteusCreateSession(aliceKey, aliceSessionId) } + assertNotNull(bobClient.transaction { it.proteusEncrypt("Hello World".encodeToByteArray(), aliceSessionId) }) } } From 24383f7191e9fa8c68ca31e2e3149d31bd3cd8fa Mon Sep 17 00:00:00 2001 From: Jacob Persson <7156+typfel@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:43:45 +0100 Subject: [PATCH 2/2] refactor: move uniffi generated code into a separate library By moving the code into its own library we can control the visibility by importing it into our public facing library as an implementation dependency. This makes our wrapper API the only visible API to the library consumer. --- .github/workflows/publish-android.yml | 1 + .github/workflows/publish-jvm.yml | 1 + .gitignore | 6 +- crypto-ffi/Makefile.toml | 12 +-- crypto-ffi/bindings/android/build.gradle.kts | 37 +-------- crypto-ffi/bindings/gradle/libs.versions.toml | 4 +- crypto-ffi/bindings/jvm/build.gradle.kts | 30 +------ crypto-ffi/bindings/settings.gradle.kts | 2 +- .../bindings/uniffi-android/build.gradle.kts | 80 +++++++++++++++++++ .../bindings/uniffi-android/gradle.properties | 1 + .../bindings/uniffi-jvm/build.gradle.kts | 46 +++++++++++ .../bindings/uniffi-jvm/gradle.properties | 1 + 12 files changed, 141 insertions(+), 80 deletions(-) create mode 100644 crypto-ffi/bindings/uniffi-android/build.gradle.kts create mode 100644 crypto-ffi/bindings/uniffi-android/gradle.properties create mode 100644 crypto-ffi/bindings/uniffi-jvm/build.gradle.kts create mode 100644 crypto-ffi/bindings/uniffi-jvm/gradle.properties diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish-android.yml index 1a90eeb514..cbbb3b73f1 100644 --- a/.github/workflows/publish-android.yml +++ b/.github/workflows/publish-android.yml @@ -51,6 +51,7 @@ jobs: - name: Publish package run: | cd crypto-ffi/bindings + ./gradlew uniffi-android:publishAllPublicationsToMavenCentral --no-configuration-cache ./gradlew android:publishAllPublicationsToMavenCentral --no-configuration-cache env: ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }} diff --git a/.github/workflows/publish-jvm.yml b/.github/workflows/publish-jvm.yml index 71d5626cea..d9dc4da9db 100644 --- a/.github/workflows/publish-jvm.yml +++ b/.github/workflows/publish-jvm.yml @@ -111,6 +111,7 @@ jobs: - name: Publish package run: | cd crypto-ffi/bindings + ./gradlew :uniffi-jvm:publishAllPublicationsToMavenCentral --no-configuration-cache ./gradlew :jvm:publishAllPublicationsToMavenCentral --no-configuration-cache env: ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }} diff --git a/.gitignore b/.gitignore index 5468cca36d..b94bc0cacf 100644 --- a/.gitignore +++ b/.gitignore @@ -33,10 +33,8 @@ DerivedData/ .exrc # Kotlin -crypto-ffi/bindings/jvm/src/main/kotlin/com/wire/crypto/CoreCrypto.kt -crypto-ffi/bindings/jvm/src/main/kotlin/uniffi/core_crypto/core_crypto.kt -crypto-ffi/bindings/android/src/main/kotlin/com/wire/crypto/CoreCrypto.kt -crypto-ffi/bindings/android/src/main/kotlin/uniffi/core_crypto/core_crypto.kt +crypto-ffi/bindings/uniffi-jvm/src +crypto-ffi/bindings/uniffi-android/src # Test databases leftovers *.edb diff --git a/crypto-ffi/Makefile.toml b/crypto-ffi/Makefile.toml index cb74791afb..33b3d56a7a 100644 --- a/crypto-ffi/Makefile.toml +++ b/crypto-ffi/Makefile.toml @@ -152,19 +152,17 @@ args = [ "generate", "--config", "uniffi-android.toml", "--language", "kotlin", - "--out-dir", "./bindings/android/src/main/kotlin/", + "--out-dir", "./bindings/uniffi-android/src/main/kotlin/", "--library", "../target/release/libcore_crypto_ffi.${LIBRARY_EXTENSION}" ] [tasks.ffi-kotlin-android] dependencies = ["compile-ffi-kotlin-android"] script = ''' - mv ./bindings/android/src/main/kotlin/com/wire/crypto/core_crypto_ffi.kt ./bindings/android/src/main/kotlin/com/wire/crypto/CoreCrypto.kt - perl -i \ -pe 's/\bCryptoException\b/CryptoError/g;' \ -pe 's/\bE2eIdentityException\b/E2eIdentityError/g;' \ - ./bindings/android/src/main/kotlin/uniffi/core_crypto/core_crypto.kt + ./bindings/uniffi-android/src/main/kotlin/uniffi/core_crypto/core_crypto.kt ''' [tasks.compile-ffi-kotlin-jvm] @@ -177,19 +175,17 @@ args = [ "--bin", "uniffi-bindgen", "generate", "--language", "kotlin", - "--out-dir", "./bindings/jvm/src/main/kotlin/", + "--out-dir", "./bindings/uniffi-jvm/src/main/kotlin/", "--library", "../target/release/libcore_crypto_ffi.${LIBRARY_EXTENSION}" ] [tasks.ffi-kotlin-jvm] dependencies = ["compile-ffi-kotlin-jvm"] script = ''' - mv ./bindings/jvm/src/main/kotlin/com/wire/crypto/core_crypto_ffi.kt ./bindings/jvm/src/main/kotlin/com/wire/crypto/CoreCrypto.kt - perl -i \ -pe 's/\bCryptoException\b/CryptoError/g;' \ -pe 's/\bE2eIdentityException\b/E2eIdentityError/g;' \ - ./bindings/jvm/src/main/kotlin/uniffi/core_crypto/core_crypto.kt + ./bindings/uniffi-jvm/src/main/kotlin/uniffi/core_crypto/core_crypto.kt ''' [tasks.ffi] diff --git a/crypto-ffi/bindings/android/build.gradle.kts b/crypto-ffi/bindings/android/build.gradle.kts index f4fb41202f..de8361a67b 100644 --- a/crypto-ffi/bindings/android/build.gradle.kts +++ b/crypto-ffi/bindings/android/build.gradle.kts @@ -15,15 +15,14 @@ val copyBindings by tasks.register("copyBindings") { group = "uniffi" from(kotlinSources) include("**/*") - exclude("**/CoreCrypto.kt", "**/core_crypto.kt") into(generatedDir) } dependencies { + implementation(project(":uniffi-android")) implementation(platform(kotlin("bom"))) implementation(platform(libs.coroutines.bom)) implementation(kotlin("stdlib-jdk7")) - implementation("${libs.jna.get()}@aar") implementation(libs.appCompat) implementation(libs.ktx.core) implementation(libs.coroutines.core) @@ -59,31 +58,6 @@ android { } } -val processedResourcesDir = buildDir.resolve("processedResources") - -fun registerCopyJvmBinaryTask(target: String, jniTarget: String, include: String = "*.so"): TaskProvider = - tasks.register("copy-${target}") { - group = "uniffi" - from(projectDir.resolve("../../../target/${target}/release")) - include(include) - into(processedResourcesDir.resolve(jniTarget)) - } - -val copyBinariesTasks = listOf( - registerCopyJvmBinaryTask("aarch64-linux-android", "arm64-v8a"), - registerCopyJvmBinaryTask("armv7-linux-androideabi", "armeabi-v7a"), - registerCopyJvmBinaryTask("x86_64-linux-android", "x86_64") -) - -project.afterEvaluate { - tasks.getByName("mergeReleaseJniLibFolders") { dependsOn(copyBinariesTasks) } - tasks.getByName("mergeDebugJniLibFolders") { dependsOn(copyBinariesTasks) } -} - -tasks.withType { - dependsOn(copyBinariesTasks) -} - tasks.withType { dependsOn(copyBindings) } @@ -92,11 +66,6 @@ tasks.withType { dependsOn(copyBindings) } -tasks.withType { - enabled = false // FIXME: find a way to do this at some point - dependsOn(copyBinariesTasks) -} - kotlin.sourceSets.getByName("main").apply { kotlin.srcDir(generatedDir.resolve("main")) } @@ -105,10 +74,6 @@ kotlin.sourceSets.getByName("androidTest").apply { kotlin.srcDir(generatedDir.resolve("test")) } -android.sourceSets.getByName("main").apply { - jniLibs.srcDir(processedResourcesDir) -} - // Allows skipping signing jars published to 'MavenLocal' repository tasks.withType().configureEach { if (System.getenv("CI") == null) { // i.e. not in Github Action runner diff --git a/crypto-ffi/bindings/gradle/libs.versions.toml b/crypto-ffi/bindings/gradle/libs.versions.toml index a60a15a0f9..6589086aca 100644 --- a/crypto-ffi/bindings/gradle/libs.versions.toml +++ b/crypto-ffi/bindings/gradle/libs.versions.toml @@ -2,14 +2,14 @@ kotlin = "1.9.0" app-compat = "1.6.1" coroutines = "1.7.3" -jna = "5.13.0" +jna = "5.14.0" ktx-core = "1.10.1" slf4j = "2.0.7" assertj = "3.24.2" espresso = "3.5.1" android-junit = "1.1.5" android-logback = "2.0.0" -android-tools = "8.0.0" +android-tools = "8.1.1" sdk-compile = "34" sdk-min = "26" gradle = "8.2.1" diff --git a/crypto-ffi/bindings/jvm/build.gradle.kts b/crypto-ffi/bindings/jvm/build.gradle.kts index 72c9da9606..ca6ed5bd2a 100644 --- a/crypto-ffi/bindings/jvm/build.gradle.kts +++ b/crypto-ffi/bindings/jvm/build.gradle.kts @@ -1,6 +1,3 @@ -import org.gradle.api.tasks.testing.logging.TestExceptionFormat -import org.gradle.api.tasks.testing.logging.TestLogEvent - plugins { kotlin("jvm") id("java-library") @@ -13,38 +10,13 @@ java { } dependencies { - implementation(platform(kotlin("bom"))) - implementation(platform(libs.coroutines.bom)) - implementation(kotlin("stdlib-jdk7")) - implementation(libs.jna) implementation(libs.coroutines.core) + implementation(project(":uniffi-jvm")) testImplementation(kotlin("test")) testImplementation(libs.coroutines.test) testImplementation(libs.assertj.core) } -val processedResourcesDir = buildDir.resolve("processedResources") - -fun registerCopyJvmBinaryTask(target: String, jniTarget: String, include: String = "*.so"): TaskProvider = - tasks.register("copy-${target}") { - group = "uniffi" - from(projectDir.resolve("../../../target/${target}/release")) - include(include) - into(processedResourcesDir.resolve(jniTarget)) - } - -val copyBinariesTasks = listOf( - registerCopyJvmBinaryTask("x86_64-unknown-linux-gnu", "linux-x86-64"), - registerCopyJvmBinaryTask("aarch64-apple-darwin", "darwin-aarch64", "*.dylib"), - registerCopyJvmBinaryTask("x86_64-apple-darwin", "darwin-x86-64", "*.dylib"), -) - -tasks.withType { dependsOn(copyBinariesTasks) } - -tasks.withType { dependsOn(copyBinariesTasks) } - -sourceSets { main { resources { srcDir(processedResourcesDir) } } } - // Allows skipping signing jars published to 'MavenLocal' repository project.afterEvaluate { tasks.named("signMavenPublication").configure { diff --git a/crypto-ffi/bindings/settings.gradle.kts b/crypto-ffi/bindings/settings.gradle.kts index 0d5cfd834c..f125a7e034 100644 --- a/crypto-ffi/bindings/settings.gradle.kts +++ b/crypto-ffi/bindings/settings.gradle.kts @@ -7,4 +7,4 @@ pluginManagement { } } -include("jvm", "android") +include("jvm", "android", "uniffi-jvm", "uniffi-android") diff --git a/crypto-ffi/bindings/uniffi-android/build.gradle.kts b/crypto-ffi/bindings/uniffi-android/build.gradle.kts new file mode 100644 index 0000000000..159c582a7d --- /dev/null +++ b/crypto-ffi/bindings/uniffi-android/build.gradle.kts @@ -0,0 +1,80 @@ +plugins { + id("com.android.library") + kotlin("android") + id("com.vanniktech.maven.publish") +} + +dependencies { + implementation(platform(kotlin("bom"))) + implementation(platform(libs.coroutines.bom)) + implementation(kotlin("stdlib-jdk7")) + implementation("${libs.jna.get()}@aar") + implementation(libs.appCompat) + implementation(libs.ktx.core) + implementation(libs.coroutines.core) + implementation(libs.slf4j) + testImplementation(kotlin("test")) + testImplementation(libs.android.logback) + testImplementation(libs.android.junit) + testImplementation(libs.espresso) + testImplementation(libs.coroutines.test) + testImplementation(libs.assertj.core) +} + +android { + namespace = "com.wire.crypto" + compileSdk = libs.versions.sdk.compile.get().toInt() + defaultConfig { + minSdk = libs.versions.sdk.min.get().toInt() + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles(file("proguard-android-optimize.txt"), file("proguard-rules.pro")) + } + } +} + +val processedResourcesDir = buildDir.resolve("processedResources") + +fun registerCopyJvmBinaryTask(target: String, jniTarget: String, include: String = "*.so"): TaskProvider = + tasks.register("copy-${target}") { + group = "uniffi" + from(projectDir.resolve("../../../target/${target}/release")) + include(include) + into(processedResourcesDir.resolve(jniTarget)) + } + +val copyBinariesTasks = listOf( + registerCopyJvmBinaryTask("aarch64-linux-android", "arm64-v8a"), + registerCopyJvmBinaryTask("armv7-linux-androideabi", "armeabi-v7a"), + registerCopyJvmBinaryTask("x86_64-linux-android", "x86_64") +) + +project.afterEvaluate { + tasks.getByName("mergeReleaseJniLibFolders") { dependsOn(copyBinariesTasks) } + tasks.getByName("mergeDebugJniLibFolders") { dependsOn(copyBinariesTasks) } +} + +tasks.withType { + dependsOn(copyBinariesTasks) +} + +android.sourceSets.getByName("main").apply { + jniLibs.srcDir(processedResourcesDir) +} + +// Allows skipping signing jars published to 'MavenLocal' repository +tasks.withType().configureEach { + if (System.getenv("CI") == null) { // i.e. not in Github Action runner + enabled = false + } +} diff --git a/crypto-ffi/bindings/uniffi-android/gradle.properties b/crypto-ffi/bindings/uniffi-android/gradle.properties new file mode 100644 index 0000000000..64d9a855e2 --- /dev/null +++ b/crypto-ffi/bindings/uniffi-android/gradle.properties @@ -0,0 +1 @@ +POM_ARTIFACT_ID=core-crypto-uniffi-android diff --git a/crypto-ffi/bindings/uniffi-jvm/build.gradle.kts b/crypto-ffi/bindings/uniffi-jvm/build.gradle.kts new file mode 100644 index 0000000000..80bd2ff611 --- /dev/null +++ b/crypto-ffi/bindings/uniffi-jvm/build.gradle.kts @@ -0,0 +1,46 @@ +plugins { + kotlin("jvm") + id("java-library") + id("com.vanniktech.maven.publish") +} + +dependencies { + implementation(platform(kotlin("bom"))) + implementation(platform(libs.coroutines.bom)) + implementation(kotlin("stdlib-jdk7")) + implementation(libs.jna) + implementation(libs.coroutines.core) + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") +} + +val processedResourcesDir = buildDir.resolve("processedResources") + +fun registerCopyJvmBinaryTask(target: String, jniTarget: String, include: String = "*.so"): TaskProvider = + tasks.register("copy-${target}") { + group = "uniffi" + from(projectDir.resolve("../../../target/${target}/release")) + include(include) + into(processedResourcesDir.resolve(jniTarget)) + } + +val copyBinariesTasks = listOf( + registerCopyJvmBinaryTask("x86_64-unknown-linux-gnu", "linux-x86-64"), + registerCopyJvmBinaryTask("aarch64-apple-darwin", "darwin-aarch64", "*.dylib"), + registerCopyJvmBinaryTask("x86_64-apple-darwin", "darwin-x86-64", "*.dylib"), +) + +tasks.withType { dependsOn(copyBinariesTasks) } + +tasks.withType { dependsOn(copyBinariesTasks) } + +sourceSets { main { resources { srcDir(processedResourcesDir) } } } + +// Allows skipping signing jars published to 'MavenLocal' repository +project.afterEvaluate { + tasks.named("signMavenPublication").configure { + if (System.getenv("CI") == null) { // i.e. not in Github Action runner + enabled = false + } + } +} diff --git a/crypto-ffi/bindings/uniffi-jvm/gradle.properties b/crypto-ffi/bindings/uniffi-jvm/gradle.properties new file mode 100644 index 0000000000..7aec02137d --- /dev/null +++ b/crypto-ffi/bindings/uniffi-jvm/gradle.properties @@ -0,0 +1 @@ +POM_ARTIFACT_ID=core-crypto-uniffi-jvm