-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!: regular group members were also impacted by unordered messages…
…. As a consequence, messages from future epochs are now buffered. - `decryptMessage` might throw a `BufferedFutureMessage` error which you should catch & ignore - `commitAccepted` now returns an optional list of decrypted messages
- Loading branch information
Showing
24 changed files
with
8,677 additions
and
113 deletions.
There are no files selected for viewing
Empty file.
6,886 changes: 6,886 additions & 0 deletions
6,886
crypto-ffi/bindings/android/src/main/kotlin/com/wire/crypto/CoreCrypto.kt
Large diffs are not rendered by default.
Oops, something went wrong.
100 changes: 100 additions & 0 deletions
100
crypto-ffi/bindings/android/src/main/kotlin/com/wire/crypto/client/CoreCryptoCentral.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package com.wire.crypto.client | ||
|
||
import com.wire.crypto.* | ||
import java.io.File | ||
|
||
typealias EnrollmentHandle = ByteArray | ||
|
||
private class Callbacks : CoreCryptoCallbacks { | ||
|
||
override fun authorize(conversationId: ByteArray, clientId: List<UByte>): Boolean = true | ||
|
||
override fun userAuthorize( | ||
conversationId: ByteArray, | ||
externalClientId: List<UByte>, | ||
existingClients: List<List<UByte>> | ||
): Boolean = true | ||
|
||
override fun clientIsExistingGroupUser( | ||
conversationId: ByteArray, | ||
clientId: List<UByte>, | ||
existingClients: List<List<UByte>>, | ||
parentConversationClients: List<List<UByte>>? | ||
): Boolean = true | ||
} | ||
|
||
@Suppress("TooManyFunctions") | ||
@OptIn(ExperimentalUnsignedTypes::class) | ||
class CoreCryptoCentral private constructor(private val cc: CoreCrypto, private val rootDir: String) { | ||
suspend fun proteusClient(): ProteusClient = ProteusClientImpl(cc, rootDir) | ||
|
||
suspend fun mlsClient(clientId: ClientId): MLSClient = MLSClient(cc).apply { mlsInit(clientId) } | ||
|
||
suspend fun e2eiNewEnrollment( | ||
clientId: String, | ||
displayName: String, | ||
handle: String, | ||
expiryDays: UInt, | ||
ciphersuite: Ciphersuite, | ||
): E2EIClient { | ||
return E2EIClient(cc.e2eiNewEnrollment(clientId, displayName, handle, expiryDays, ciphersuite.lower())) | ||
} | ||
|
||
suspend fun e2eiNewActivationEnrollment( | ||
clientId: String, | ||
displayName: String, | ||
handle: String, | ||
expiryDays: UInt, | ||
ciphersuite: Ciphersuite, | ||
): E2EIClient { | ||
return E2EIClient( | ||
cc.e2eiNewActivationEnrollment( | ||
clientId, | ||
displayName, | ||
handle, | ||
expiryDays, | ||
ciphersuite.lower() | ||
) | ||
) | ||
} | ||
|
||
suspend fun e2eiNewRotateEnrollment( | ||
clientId: String, | ||
expiryDays: UInt, | ||
ciphersuite: Ciphersuite, | ||
displayName: String? = null, | ||
handle: String? = null, | ||
): E2EIClient { | ||
return E2EIClient(cc.e2eiNewRotateEnrollment(clientId, displayName, handle, expiryDays, ciphersuite.lower())) | ||
} | ||
|
||
suspend fun e2eiMlsInitOnly(enrollment: E2EIClient, certificateChain: String): MLSClient { | ||
cc.e2eiMlsInitOnly(enrollment.delegate, certificateChain) | ||
return MLSClient(cc) | ||
} | ||
|
||
suspend fun e2eiEnrollmentStash(enrollment: E2EIClient): EnrollmentHandle { | ||
return cc.e2eiEnrollmentStash(enrollment.delegate).toUByteArray().asByteArray() | ||
} | ||
|
||
suspend fun e2eiEnrollmentStashPop(handle: EnrollmentHandle): E2EIClient { | ||
return E2EIClient(cc.e2eiEnrollmentStashPop(handle.asUByteArray().asList())) | ||
} | ||
|
||
companion object { | ||
private const val KEYSTORE_NAME = "keystore" | ||
fun CiphersuiteName.lower() = (ordinal + 1).toUShort() | ||
val DEFAULT_CREDENTIAL_TYPE = MlsCredentialType.BASIC | ||
val DEFAULT_CIPHERSUITE = CiphersuiteName.MLS_128_DHKEMX25519_AES128GCM_SHA256_ED25519.lower() | ||
val DEFAULT_CIPHERSUITES = listOf(DEFAULT_CIPHERSUITE) | ||
|
||
suspend operator fun invoke(rootDir: String, databaseKey: String): CoreCryptoCentral { | ||
val path = "$rootDir/$KEYSTORE_NAME" | ||
File(rootDir).mkdirs() | ||
val cc = coreCryptoDeferredInit(path, databaseKey, DEFAULT_CIPHERSUITES) | ||
cc.setCallbacks(Callbacks()) | ||
return CoreCryptoCentral(cc, rootDir) | ||
} | ||
} | ||
} | ||
|
2 changes: 2 additions & 0 deletions
2
crypto-ffi/bindings/android/src/main/kotlin/com/wire/crypto/client/E2EIModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
package com.wire.crypto.client | ||
|
127 changes: 127 additions & 0 deletions
127
crypto-ffi/bindings/android/src/main/kotlin/com/wire/crypto/client/E2eiClient.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package com.wire.crypto.client | ||
|
||
import com.wire.crypto.WireE2eIdentity | ||
import com.wire.crypto.client.AcmeChallenge.Companion.toAcmeChallenge | ||
import com.wire.crypto.client.AcmeDirectory.Companion.toAcmeDirectory | ||
import com.wire.crypto.client.NewAcmeAuthz.Companion.toNewAcmeAuthz | ||
import com.wire.crypto.client.NewAcmeOrder.Companion.toNewAcmeOrder | ||
|
||
typealias JsonRawData = ByteArray | ||
typealias DpopToken = String | ||
|
||
data class AcmeDirectory( | ||
val newNonce: String, | ||
val newAccount: String, | ||
val newOrder: String | ||
) { | ||
constructor(delegate: com.wire.crypto.AcmeDirectory) : this( | ||
delegate.newNonce, | ||
delegate.newAccount, | ||
delegate.newOrder | ||
) | ||
|
||
companion object { | ||
fun com.wire.crypto.AcmeDirectory.toAcmeDirectory() = AcmeDirectory(this) | ||
} | ||
} | ||
|
||
data class NewAcmeOrder(val delegate: JsonRawData, val authorizations: List<String>) { | ||
|
||
@OptIn(ExperimentalUnsignedTypes::class) | ||
constructor(delegate: com.wire.crypto.NewAcmeOrder) : this( | ||
delegate.delegate.toUByteArray().asByteArray(), | ||
delegate.authorizations, | ||
) | ||
|
||
companion object { | ||
fun com.wire.crypto.NewAcmeOrder.toNewAcmeOrder() = NewAcmeOrder(this) | ||
} | ||
} | ||
|
||
data class AcmeChallenge(val delegate: JsonRawData, val url: String) { | ||
@OptIn(ExperimentalUnsignedTypes::class) | ||
constructor(delegate: com.wire.crypto.AcmeChallenge) : this( | ||
delegate.delegate.toUByteArray().asByteArray(), delegate.url | ||
) | ||
|
||
companion object { | ||
fun com.wire.crypto.AcmeChallenge.toAcmeChallenge() = AcmeChallenge(this) | ||
} | ||
} | ||
|
||
data class NewAcmeAuthz( | ||
val identifier: String, | ||
val wireOidcChallenge: AcmeChallenge?, | ||
val wireDpopChallenge: AcmeChallenge? | ||
) { | ||
constructor(delegate: com.wire.crypto.NewAcmeAuthz) : this( | ||
delegate.identifier, | ||
delegate.wireOidcChallenge?.toAcmeChallenge(), | ||
delegate.wireDpopChallenge?.toAcmeChallenge(), | ||
) | ||
|
||
companion object { | ||
fun com.wire.crypto.NewAcmeAuthz.toNewAcmeAuthz() = NewAcmeAuthz(this) | ||
} | ||
} | ||
|
||
@Suppress("TooManyFunctions") | ||
@OptIn(ExperimentalUnsignedTypes::class) | ||
class E2EIClient(val delegate: WireE2eIdentity) { | ||
|
||
private val defaultDPoPTokenExpiry: UInt = 30U | ||
|
||
suspend fun directoryResponse(directory: JsonRawData) = | ||
delegate.directoryResponse(directory.toUByteList()).toAcmeDirectory() | ||
|
||
suspend fun newAccountRequest(previousNonce: String) = | ||
delegate.newAccountRequest(previousNonce).toByteArray() | ||
|
||
suspend fun accountResponse(account: JsonRawData) = | ||
delegate.newAccountResponse(account.toUByteList()) | ||
|
||
suspend fun newOrderRequest(previousNonce: String) = | ||
delegate.newOrderRequest(previousNonce).toByteArray() | ||
|
||
suspend fun newOrderResponse(order: JsonRawData) = | ||
delegate.newOrderResponse(order.toUByteList()).toNewAcmeOrder() | ||
|
||
suspend fun newAuthzRequest(url: String, previousNonce: String) = | ||
delegate.newAuthzRequest(url, previousNonce).toByteArray() | ||
|
||
suspend fun authzResponse(authz: JsonRawData) = | ||
delegate.newAuthzResponse(authz.toUByteList()).toNewAcmeAuthz() | ||
|
||
suspend fun createDpopToken(backendNonce: String) = | ||
delegate.createDpopToken(expirySecs = defaultDPoPTokenExpiry, backendNonce) | ||
|
||
suspend fun newDpopChallengeRequest(accessToken: String, previousNonce: String) = | ||
delegate.newDpopChallengeRequest(accessToken, previousNonce).toByteArray() | ||
|
||
suspend fun newOidcChallengeRequest(idToken: String, previousNonce: String) = | ||
delegate.newOidcChallengeRequest(idToken, previousNonce).toByteArray() | ||
|
||
suspend fun challengeResponse(challenge: JsonRawData) = | ||
delegate.newChallengeResponse(challenge.toUByteList()) | ||
|
||
suspend fun checkOrderRequest(orderUrl: String, previousNonce: String) = | ||
delegate.checkOrderRequest(orderUrl, previousNonce).toByteArray() | ||
|
||
suspend fun checkOrderResponse(order: JsonRawData) = | ||
delegate.checkOrderResponse(order.toUByteList()) | ||
|
||
suspend fun finalizeRequest(previousNonce: String) = | ||
delegate.finalizeRequest(previousNonce).toByteArray() | ||
|
||
suspend fun finalizeResponse(finalize: JsonRawData) = | ||
delegate.finalizeResponse(finalize.toUByteList()) | ||
|
||
suspend fun certificateRequest(previousNonce: String) = | ||
delegate.certificateRequest(previousNonce).toByteArray() | ||
|
||
companion object { | ||
|
||
fun ByteArray.toUByteList(): List<UByte> = map { it.toUByte() } | ||
fun List<UByte>.toByteArray() = toUByteArray().asByteArray() | ||
} | ||
} |
161 changes: 161 additions & 0 deletions
161
crypto-ffi/bindings/android/src/main/kotlin/com/wire/crypto/client/MLSClient.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
/* | ||
* 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_CIPHERSUITE | ||
import com.wire.crypto.client.CoreCryptoCentral.Companion.DEFAULT_CIPHERSUITES | ||
import kotlin.time.Duration | ||
import kotlin.time.DurationUnit | ||
import kotlin.time.toDuration | ||
|
||
@Suppress("TooManyFunctions") | ||
@OptIn(ExperimentalUnsignedTypes::class) | ||
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 | ||
) | ||
|
||
} | ||
|
||
suspend fun mlsInit(id: ClientId) { | ||
cc.mlsInit(id.lower(), DEFAULT_CIPHERSUITES) | ||
} | ||
|
||
suspend fun getPublicKey(ciphersuite: Ciphersuite): SignaturePublicKey { | ||
return cc.clientPublicKey(ciphersuite.lower()).toSignaturePublicKey() | ||
} | ||
|
||
suspend fun generateKeyPackages( | ||
ciphersuite: Ciphersuite, | ||
credentialType: CredentialType, | ||
amount: UInt | ||
): List<MLSKeyPackage> { | ||
return cc.clientKeypackages(ciphersuite.lower(), credentialType.lower(), amount) | ||
.map { it.toMLSKeyPackage() } | ||
} | ||
|
||
suspend fun validKeyPackageCount(ciphersuite: Ciphersuite, credentialType: CredentialType): ULong { | ||
return cc.clientValidKeypackagesCount(ciphersuite.lower(), credentialType.lower()) | ||
} | ||
|
||
suspend fun updateKeyingMaterial(id: MLSGroupId) = CommitBundle(cc.updateKeyingMaterial(id.lower())) | ||
|
||
suspend fun conversationExists(id: MLSGroupId): Boolean = cc.conversationExists(id.lower()) | ||
|
||
suspend fun conversationEpoch(id: MLSGroupId): ULong = cc.conversationEpoch(id.lower()) | ||
|
||
suspend fun joinConversation( | ||
id: MLSGroupId, | ||
epoch: ULong, | ||
ciphersuite: Ciphersuite, | ||
credentialType: CredentialType, | ||
): MlsMessage { | ||
return cc.newExternalAddProposal(id.lower(), epoch, ciphersuite.lower(), credentialType.lower()).toMlsMessage() | ||
} | ||
|
||
suspend fun joinByExternalCommit(groupInfo: GroupInfo, credentialType: CredentialType): CommitBundle { | ||
return CommitBundle( | ||
cc.joinByExternalCommit( | ||
groupInfo.lower(), | ||
defaultGroupConfiguration, | ||
credentialType.lower() | ||
) | ||
) | ||
} | ||
|
||
suspend fun mergePendingGroupFromExternalCommit(id: MLSGroupId): List<DecryptedMessage>? { | ||
return cc.mergePendingGroupFromExternalCommit(id.lower())?.map { DecryptedMessage(it) } | ||
} | ||
|
||
suspend fun clearPendingGroupExternalCommit(id: MLSGroupId) = cc.clearPendingGroupFromExternalCommit(id.lower()) | ||
|
||
suspend fun createConversation( | ||
id: MLSGroupId, | ||
creatorCredentialType: CredentialType, | ||
externalSenders: List<ExternalSenderKey> = emptyList(), | ||
perDomainTrustAnchors: List<com.wire.crypto.PerDomainTrustAnchor> = emptyList(), | ||
) { | ||
val cfg = com.wire.crypto.ConversationConfiguration( | ||
DEFAULT_CIPHERSUITE, | ||
externalSenders.map { it.lower() }, | ||
defaultGroupConfiguration, | ||
perDomainTrustAnchors | ||
) | ||
|
||
cc.createConversation(id.lower(), creatorCredentialType.lower(), cfg) | ||
} | ||
|
||
suspend fun wipeConversation(id: MLSGroupId) = cc.wipeConversation(id.lower()) | ||
|
||
suspend fun processWelcomeMessage(welcome: Welcome): MLSGroupId { | ||
return cc.processWelcomeMessage(welcome.lower(), defaultGroupConfiguration).toGroupId() | ||
} | ||
|
||
suspend fun encryptMessage(id: MLSGroupId, message: PlaintextMessage): MlsMessage { | ||
return cc.encryptMessage(id.lower(), message.lower()).toMlsMessage() | ||
} | ||
|
||
suspend fun updateTrustAnchorsFromConversation( | ||
id: MLSGroupId, | ||
removeDomainNames: List<String>, | ||
addTrustAnchors: List<com.wire.crypto.PerDomainTrustAnchor>, | ||
): CommitBundle { | ||
return CommitBundle( | ||
cc.updateTrustAnchorsFromConversation(id.lower(), removeDomainNames, addTrustAnchors) | ||
) | ||
} | ||
|
||
suspend fun decryptMessage(id: MLSGroupId, message: MlsMessage): DecryptedMessage { | ||
return DecryptedMessage(cc.decryptMessage(id.lower(), message.lower())) | ||
} | ||
|
||
suspend fun commitAccepted(id: MLSGroupId) = cc.commitAccepted(id.lower()) | ||
|
||
suspend fun commitPendingProposals(id: MLSGroupId) = cc.commitPendingProposals(id.lower())?.let { CommitBundle(it) } | ||
|
||
suspend fun clearPendingCommit(id: MLSGroupId) = cc.clearPendingCommit(id.lower()) | ||
|
||
suspend fun members(id: MLSGroupId): List<ClientId> = cc.getClientIds(id.lower()).map { it.toClientId() } | ||
|
||
suspend fun addMember(id: MLSGroupId, members: Map<ClientId, MLSKeyPackage>): CommitBundle { | ||
val invitees = members.map { (clientId, kp) -> com.wire.crypto.Invitee(clientId.lower(), kp.lower()) } | ||
return CommitBundle(cc.addClientsToConversation(id.lower(), invitees)) | ||
} | ||
|
||
suspend fun removeMember(id: MLSGroupId, members: List<ClientId>): CommitBundle { | ||
val clientIds = members.map { it.lower() } | ||
return CommitBundle(cc.removeClientsFromConversation(id.lower(), clientIds)) | ||
} | ||
|
||
suspend fun deriveSecret(id: MLSGroupId, keyLength: UInt): AvsSecret { | ||
return cc.exportSecretKey(id.lower(), keyLength).toAvsSecret() | ||
} | ||
|
||
suspend fun e2eiConversationState(id: MLSGroupId): com.wire.crypto.E2eiConversationState { | ||
return cc.e2eiConversationState(id.lower()) | ||
} | ||
|
||
suspend fun e2eiIsEnabled(ciphersuite: Ciphersuite): Boolean = cc.e2eiIsEnabled(ciphersuite.lower()) | ||
} |
Oops, something went wrong.