crypto: Add a Rust based CrossSigningService

This commit is contained in:
Damir Jelić 2021-08-11 15:27:54 +02:00
parent b012a0ff75
commit c85847df57
7 changed files with 909 additions and 149 deletions

View File

@ -40,6 +40,7 @@ import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.api.session.events.model.Content
@ -51,7 +52,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService
import org.matrix.android.sdk.internal.auth.registration.handleUIA
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
@ -66,6 +67,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent
import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
@ -78,6 +80,8 @@ import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask
import org.matrix.android.sdk.internal.crypto.verification.RustVerificationService
import org.matrix.android.sdk.internal.di.DeviceId
import org.matrix.android.sdk.internal.di.MoshiProvider
@ -98,6 +102,8 @@ import timber.log.Timber
import uniffi.olm.OutgoingVerificationRequest
import uniffi.olm.Request
import uniffi.olm.RequestType
import uniffi.olm.SignatureUploadRequest
import uniffi.olm.UploadSigningKeysRequest
import java.io.File
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean
@ -109,7 +115,9 @@ internal class RequestSender @Inject constructor(
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask,
private val uploadKeysTask: UploadKeysTask,
private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
private val signaturesUploadTask: UploadSignaturesTask,
private val sendVerificationMessageTask: Lazy<DefaultSendVerificationMessageTask>,
private val uploadSigningKeysTask: UploadSigningKeysTask,
) {
suspend fun claimKeys(request: Request.KeysClaim): String {
@ -161,6 +169,55 @@ internal class RequestSender @Inject constructor(
return this.sendVerificationMessageTask.get().execute(params)
}
suspend fun sendSignatureUpload(request: Request.SignatureUpload) {
sendSignatureUpload(request.body)
}
suspend fun sendSignatureUpload(request: SignatureUploadRequest) {
sendSignatureUpload(request.body)
}
private suspend fun sendSignatureUpload(body: String) {
val adapter = MoshiProvider.providesMoshi().adapter<Map<String, Map<String, Any>>>(Map::class.java)
val signatures = adapter.fromJson(body)!!
val params = UploadSignaturesTask.Params(signatures)
this.signaturesUploadTask.execute(params)
}
suspend fun uploadCrossSigningKeys(
request: UploadSigningKeysRequest,
interactiveAuthInterceptor: UserInteractiveAuthInterceptor?
) {
val adapter = MoshiProvider.providesMoshi().adapter<RestKeyInfo>(RestKeyInfo::class.java)
val masterKey = adapter.fromJson(request.masterKey)!!.toCryptoModel()
val selfSigningKey = adapter.fromJson(request.selfSigningKey)!!.toCryptoModel()
val userSigningKey = adapter.fromJson(request.userSigningKey)!!.toCryptoModel()
val uploadSigningKeysParams = UploadSigningKeysTask.Params(
masterKey,
userSigningKey,
selfSigningKey,
null
)
try {
uploadSigningKeysTask.execute(uploadSigningKeysParams)
} catch (failure: Throwable) {
if (interactiveAuthInterceptor == null
|| !handleUIA(
failure = failure,
interceptor = interactiveAuthInterceptor,
retryBlock = { authUpdate ->
uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate))
}
)
) {
Timber.d("## UIA: propagate failure")
throw failure
}
}
}
suspend fun sendToDevice(request: Request.ToDevice) {
sendToDevice(request.eventType, request.body, request.requestId)
}
@ -208,7 +265,6 @@ internal class DefaultCryptoService @Inject constructor(
private val mxCryptoConfig: MXCryptoConfig,
// The key backup service.
private val keysBackupService: DefaultKeysBackupService,
private val crossSigningService: DefaultCrossSigningService,
// Actions
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
// Tasks
@ -232,6 +288,9 @@ internal class DefaultCryptoService @Inject constructor(
// The verification service.
private var verificationService: RustVerificationService? = null
// The cross signing service.
private var crossSigningService: RustCrossSigningService? = null
private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver()
// Locks for some of our operations
@ -294,7 +353,9 @@ internal class DefaultCryptoService @Inject constructor(
return if (longFormat) "Rust SDK 0.3" else "0.3"
}
override fun getMyDevice(): CryptoDeviceInfo = this.olmMachine!!.ownDevice()
override fun getMyDevice(): CryptoDeviceInfo {
return runBlocking { olmMachine!!.ownDevice() }
}
override fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>) {
getDevicesTask
@ -394,7 +455,8 @@ internal class DefaultCryptoService @Inject constructor(
setRustLogger()
val machine = OlmMachine(userId, deviceId!!, dataDir, deviceObserver, sender)
olmMachine = machine
verificationService = RustVerificationService(machine, this.sender)
verificationService = RustVerificationService(machine)
crossSigningService = RustCrossSigningService(machine)
Timber.v(
"## CRYPTO | Successfully started up an Olm machine for " +
"${userId}, ${deviceId}, identity keys: ${this.olmMachine?.identityKeys()}")
@ -453,7 +515,13 @@ internal class DefaultCryptoService @Inject constructor(
return verificationService!!
}
override fun crossSigningService() = crossSigningService
override fun crossSigningService(): CrossSigningService {
if (crossSigningService == null) {
internalStart()
}
return crossSigningService!!
}
/**
* A sync response has been received
@ -461,6 +529,15 @@ internal class DefaultCryptoService @Inject constructor(
suspend fun onSyncCompleted() {
if (isStarted()) {
sendOutgoingRequests()
// This isn't a copy paste error. Sending the outgoing requests may
// claim one-time keys and establish 1-to-1 Olm sessions with devices, while some
// outgoing requests are waiting for an Olm session to be established (e.g. forwarding
// room keys or sharing secrets).
// The second call sends out those requests that are waiting for the
// keys claim request to be sent out.
// This could be omitted but then devices might be waiting for the next
sendOutgoingRequests()
}
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
@ -491,7 +568,7 @@ internal class DefaultCryptoService @Inject constructor(
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
return runBlocking {
this@DefaultCryptoService.olmMachine?.getUserDevices(userId) ?: listOf()
this@DefaultCryptoService.olmMachine?.getCryptoDeviceInfo(userId) ?: listOf()
}
}
@ -572,7 +649,7 @@ internal class DefaultCryptoService @Inject constructor(
* @return the stored device keys for a user.
*/
override fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
return cryptoStore.getUserDevices(userId)?.values?.toMutableList() ?: ArrayList()
return this.getCryptoDeviceInfo(userId).toMutableList()
}
private fun isEncryptionEnabledForInvitedUser(): Boolean {
@ -845,36 +922,46 @@ internal class DefaultCryptoService @Inject constructor(
olmMachine!!.markRequestAsSent(request.requestId, RequestType.KEYS_CLAIM, response)
}
private suspend fun signatureUpload(request: Request.SignatureUpload) {
this.sender.sendSignatureUpload(request)
olmMachine!!.markRequestAsSent(request.requestId, RequestType.SIGNATURE_UPLOAD, "{}")
}
private suspend fun sendOutgoingRequests() {
outgoingRequestsLock.withLock {
coroutineScope {
olmMachine!!.outgoingRequests().map {
when (it) {
is Request.KeysUpload -> {
is Request.KeysUpload -> {
async {
uploadKeys(it)
}
}
is Request.KeysQuery -> {
is Request.KeysQuery -> {
async {
queryKeys(it)
}
}
is Request.ToDevice -> {
is Request.ToDevice -> {
async {
sendToDevice(it)
}
}
is Request.KeysClaim -> {
is Request.KeysClaim -> {
async {
claimKeys(it)
}
}
is Request.RoomMessage -> {
is Request.RoomMessage -> {
async {
sender.sendRoomMessage(it)
}
}
is Request.SignatureUpload -> {
async {
signatureUpload(it)
}
}
}
}.joinAll()
}

View File

@ -20,10 +20,14 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
import uniffi.olm.CryptoStoreErrorException
import uniffi.olm.Device as InnerDevice
import uniffi.olm.OlmMachine
import uniffi.olm.SignatureErrorException
import uniffi.olm.VerificationRequest
/** Class representing a device that supports E2EE in the Matrix world
@ -37,10 +41,27 @@ internal class Device(
private val sender: RequestSender,
private val listeners: ArrayList<VerificationService.Listener>,
) {
/** Request an interactive verification to begin
@Throws(CryptoStoreErrorException::class)
private suspend fun refreshData() {
val device = withContext(Dispatchers.IO) {
machine.getDevice(inner.userId, inner.deviceId)
}
if (device != null) {
this.inner = device
}
}
/**
* Request an interactive verification to begin
*
* This sends out a m.key.verification.request event over to-device messaging to
* to this device.
*
* If no specific device should be verified, but we would like to request
* verification from all our devices, the
* [org.matrix.android.sdk.internal.crypto.OwnUserIdentity.requestVerification]
* method can be used instead.
*/
@Throws(CryptoStoreErrorException::class)
suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest? {
@ -61,6 +82,10 @@ internal class Device(
*
* This sends out a m.key.verification.start event with the method set to
* m.sas.v1 to this device using to-device messaging.
*
* This method will soon be deprecated by [MSC3122](https://github.com/matrix-org/matrix-doc/pull/3122).
* The [requestVerification] method should be used instead.
*
*/
@Throws(CryptoStoreErrorException::class)
suspend fun startVerification(): SasVerification? {
@ -78,7 +103,8 @@ internal class Device(
}
}
/** Mark this device as locally trusted
/**
* Mark this device as locally trusted
*
* This won't upload any signatures, it will only mark the device as trusted
* in the local database.
@ -89,4 +115,56 @@ internal class Device(
machine.markDeviceAsTrusted(inner.userId, inner.deviceId)
}
}
/**
* Manually verify this device
*
* This will sign the device with our self-signing key and upload the signatures
* to the server.
*
* This will fail if the device doesn't belong to use or if we don't have the
* private part of our self-signing key.
*/
@Throws(SignatureErrorException::class)
suspend fun verify(): Boolean {
val request = withContext(Dispatchers.IO) {
machine.verifyDevice(inner.userId, inner.deviceId)
}
this.sender.sendSignatureUpload(request)
return true
}
/**
* Get the DeviceTrustLevel of this device
*/
@Throws(CryptoStoreErrorException::class)
suspend fun trustLevel(): DeviceTrustLevel {
refreshData()
return DeviceTrustLevel(crossSigningVerified = inner.crossSigningTrusted, locallyVerified = inner.locallyTrusted)
}
/**
* Convert this device to a CryptoDeviceInfo.
*
* This will not fetch out fresh data from the Rust side.
**/
internal fun toCryptoDeviceInfo(): CryptoDeviceInfo {
val keys = this.inner.keys.map { (keyId, key) -> "$keyId:$this.inner.deviceId" to key }.toMap()
return CryptoDeviceInfo(
this.inner.deviceId,
this.inner.userId,
this.inner.algorithms,
keys,
// The Kotlin side doesn't need to care about signatures,
// so we're not filling this out
mapOf(),
UnsignedDeviceInfo(this.inner.displayName),
DeviceTrustLevel(crossSigningVerified = this.inner.crossSigningTrusted, locallyVerified = this.inner.locallyTrusted),
this.inner.isBlocked,
// TODO
null)
}
}

View File

@ -24,26 +24,34 @@ import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.session.sync.model.DeviceListResponse
import org.matrix.android.sdk.internal.session.sync.model.DeviceOneTimeKeysCountSyncResponse
import org.matrix.android.sdk.internal.session.sync.model.ToDeviceSyncResponse
import timber.log.Timber
import uniffi.olm.CrossSigningKeyExport
import uniffi.olm.CrossSigningStatus
import uniffi.olm.CryptoStoreErrorException
import uniffi.olm.DecryptionErrorException
import uniffi.olm.Device as InnerDevice
import uniffi.olm.DeviceLists
import uniffi.olm.KeyRequestPair
import uniffi.olm.Logger
@ -51,6 +59,7 @@ import uniffi.olm.OlmMachine as InnerMachine
import uniffi.olm.ProgressListener as RustProgressListener
import uniffi.olm.Request
import uniffi.olm.RequestType
import uniffi.olm.UserIdentity as RustUserIdentity
import uniffi.olm.setLogger
class CryptoLogger : Logger {
@ -83,27 +92,34 @@ internal class LiveDevice(
}
}
fun setRustLogger() {
setLogger(CryptoLogger() as Logger)
internal class LiveUserIdentity(
internal var userId: String,
private var observer: UserIdentityUpdateObserver,
) : MutableLiveData<Optional<MXCrossSigningInfo>>() {
override fun onActive() {
observer.addUserIdentityUpdateListener(this)
}
override fun onInactive() {
observer.removeUserIdentityUpdateListener(this)
}
}
/** Convert a Rust Device into a Kotlin CryptoDeviceInfo */
private fun toCryptoDeviceInfo(device: InnerDevice): CryptoDeviceInfo {
val keys = device.keys.map { (keyId, key) -> "$keyId:$device.deviceId" to key }.toMap()
internal class LivePrivateCrossSigningKeys(
private var observer: PrivateCrossSigningKeysUpdateObserver,
) : MutableLiveData<Optional<PrivateKeysInfo>>() {
return CryptoDeviceInfo(
device.deviceId,
device.userId,
device.algorithms,
keys,
// TODO pass the signatures here, do we need this, why should the
// Kotlin side care about signatures?
mapOf(),
UnsignedDeviceInfo(device.displayName),
DeviceTrustLevel(crossSigningVerified = device.crossSigningTrusted, locallyVerified = device.locallyTrusted),
device.isBlocked,
// TODO
null)
override fun onActive() {
observer.addUserIdentityUpdateListener(this)
}
override fun onInactive() {
observer.removeUserIdentityUpdateListener(this)
}
}
fun setRustLogger() {
setLogger(CryptoLogger() as Logger)
}
internal class DeviceUpdateObserver {
@ -118,6 +134,30 @@ internal class DeviceUpdateObserver {
}
}
internal class UserIdentityUpdateObserver {
internal val listeners = ConcurrentHashMap<LiveUserIdentity, String>()
fun addUserIdentityUpdateListener(userIdentity: LiveUserIdentity) {
listeners[userIdentity] = userIdentity.userId
}
fun removeUserIdentityUpdateListener(userIdentity: LiveUserIdentity) {
listeners.remove(userIdentity)
}
}
internal class PrivateCrossSigningKeysUpdateObserver {
internal val listeners = ConcurrentHashMap<LivePrivateCrossSigningKeys, Unit>()
fun addUserIdentityUpdateListener(liveKeys: LivePrivateCrossSigningKeys) {
listeners[liveKeys] = Unit
}
fun removeUserIdentityUpdateListener(liveKeys: LivePrivateCrossSigningKeys) {
listeners.remove(liveKeys)
}
}
internal class OlmMachine(
user_id: String,
device_id: String,
@ -127,6 +167,8 @@ internal class OlmMachine(
) {
private val inner: InnerMachine = InnerMachine(user_id, device_id, path.toString())
private val deviceUpdateObserver = deviceObserver
private val userIdentityUpdateObserver = UserIdentityUpdateObserver()
private val privateKeysUpdateObserver = PrivateCrossSigningKeysUpdateObserver()
internal val verificationListeners = ArrayList<VerificationService.Listener>()
/** Get our own user ID. */
@ -148,11 +190,42 @@ internal class OlmMachine(
return this.inner
}
fun ownDevice(): CryptoDeviceInfo {
/** Update all of our live device listeners. */
private suspend fun updateLiveDevices() {
for ((liveDevice, users) in deviceUpdateObserver.listeners) {
val devices = getCryptoDeviceInfo(users)
liveDevice.postValue(devices)
}
}
private suspend fun updateLiveUserIdentities() {
for ((liveIdentity, userId) in userIdentityUpdateObserver.listeners) {
val identity = getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
liveIdentity.postValue(identity)
}
}
private suspend fun updateLivePrivateKeys() {
val keys = this.exportCrossSigningKeys().toOptional()
for (liveKeys in privateKeysUpdateObserver.listeners.keys()) {
liveKeys.postValue(keys)
}
}
/**
* Get our own device info as [CryptoDeviceInfo].
*/
suspend fun ownDevice(): CryptoDeviceInfo {
val deviceId = this.deviceId()
val keys = this.identityKeys().map { (keyId, key) -> "$keyId:$deviceId" to key }.toMap()
val crossSigningVerified = when (val ownIdentity = this.getIdentity(this.userId())) {
is OwnUserIdentity -> ownIdentity.trustsOurOwnDevice()
else -> false
}
return CryptoDeviceInfo(
this.deviceId(),
this.userId(),
@ -161,7 +234,7 @@ internal class OlmMachine(
keys,
mapOf(),
UnsignedDeviceInfo(),
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
DeviceTrustLevel(crossSigningVerified, locallyVerified = true),
false,
null)
}
@ -170,7 +243,7 @@ internal class OlmMachine(
* Get the list of outgoing requests that need to be sent to the homeserver.
*
* After the request was sent out and a successful response was received the response body
* should be passed back to the state machine using the markRequestAsSent() method.
* should be passed back to the state machine using the [markRequestAsSent] method.
*
* @return the list of requests that needs to be sent to the homeserver
*/
@ -197,6 +270,7 @@ internal class OlmMachine(
if (requestType == RequestType.KEYS_QUERY) {
updateLiveDevices()
updateLiveUserIdentities()
}
}
@ -218,24 +292,30 @@ internal class OlmMachine(
toDevice: ToDeviceSyncResponse?,
deviceChanges: DeviceListResponse?,
keyCounts: DeviceOneTimeKeysCountSyncResponse?
): ToDeviceSyncResponse =
withContext(Dispatchers.IO) {
val counts: MutableMap<String, Int> = mutableMapOf()
): ToDeviceSyncResponse {
val response = withContext(Dispatchers.IO) {
val counts: MutableMap<String, Int> = mutableMapOf()
if (keyCounts?.signedCurve25519 != null) {
counts["signed_curve25519"] = keyCounts.signedCurve25519
}
val devices =
DeviceLists(
deviceChanges?.changed ?: listOf(), deviceChanges?.left ?: listOf())
val adapter =
MoshiProvider.providesMoshi().adapter(ToDeviceSyncResponse::class.java)
val events = adapter.toJson(toDevice ?: ToDeviceSyncResponse())!!
adapter.fromJson(inner.receiveSyncChanges(events, devices, counts))!!
if (keyCounts?.signedCurve25519 != null) {
counts["signed_curve25519"] = keyCounts.signedCurve25519
}
val devices =
DeviceLists(
deviceChanges?.changed ?: listOf(), deviceChanges?.left ?: listOf())
val adapter =
MoshiProvider.providesMoshi().adapter(ToDeviceSyncResponse::class.java)
val events = adapter.toJson(toDevice ?: ToDeviceSyncResponse())!!
adapter.fromJson(inner.receiveSyncChanges(events, devices, counts))!!
}
// We may get cross signing keys over a to-device event, update our listeners.
this.updateLivePrivateKeys()
return response
}
/**
* Mark the given list of users to be tracked, triggering a key query request for them.
*
@ -251,13 +331,13 @@ internal class OlmMachine(
* Generate one-time key claiming requests for all the users we are missing sessions for.
*
* After the request was sent out and a successful response was received the response body
* should be passed back to the state machine using the markRequestAsSent() method.
* should be passed back to the state machine using the [markRequestAsSent] method.
*
* This method should be called every time before a call to shareRoomKey() is made.
* This method should be called every time before a call to [shareRoomKey] is made.
*
* @param users The list of users for which we would like to establish 1:1 Olm sessions for.
*
* @return A keys claim request that needs to be sent out to the server.
* @return A [Request.KeysClaim] request that needs to be sent out to the server.
*/
@Throws(CryptoStoreErrorException::class)
suspend fun getMissingSessions(users: List<String>): Request? =
@ -279,7 +359,7 @@ internal class OlmMachine(
* @param users The list of users which are considered to be members of the room and should
* receive the room key.
*
* @return The list of requests that need to be sent out.
* @return The list of [Request.ToDevice] that need to be sent out.
*/
@Throws(CryptoStoreErrorException::class)
suspend fun shareRoomKey(roomId: String, users: List<String>): List<Request> =
@ -288,21 +368,18 @@ internal class OlmMachine(
/**
* Encrypt the given event with the given type and content for the given room.
*
* **Note**: A room key needs to be shared with the group of users that are members in the given
* room. If this is not done this method will panic.
* **Note**: A room key needs to be shared with the group of users that are members
* in the given room. If this is not done this method will panic.
*
* The usual flow to encrypt an event using this state machine is as follows:
*
* 1. Get the one-time key claim request to establish 1:1 Olm sessions for
* ```
* the room members of the room we wish to participate in. This is done
* using the [`get_missing_sessions()`](#method.get_missing_sessions)
* method. This method call should be locked per call.
* ```
* 2. Share a room key with all the room members using the shareRoomKey().
* ```
* This method call should be locked per room.
* ```
* the room members of the room we wish to participate in. This is done
* using the [getMissingSessions] method. This method call should be locked per call.
*
* 2. Share a room key with all the room members using the [shareRoomKey].
* This method call should be locked per room.
*
* 3. Encrypt the event using this method.
*
* 4. Send the encrypted event to the server.
@ -316,7 +393,7 @@ internal class OlmMachine(
*
* @param content the JSON content of the event
*
* @return The encrypted version of the content
* @return The encrypted version of the [Content]
*/
@Throws(CryptoStoreErrorException::class)
suspend fun encrypt(roomId: String, eventType: String, content: Content): Content =
@ -334,7 +411,7 @@ internal class OlmMachine(
*
* @param event The serialized encrypted version of the event.
*
* @return the decrypted version of the event.
* @return the decrypted version of the event as a [MXEventDecryptionResult].
*/
@Throws(MXCryptoError::class)
suspend fun decryptRoomEvent(event: Event): MXEventDecryptionResult =
@ -421,9 +498,44 @@ internal class OlmMachine(
ImportRoomKeysResult(result.total, result.imported)
}
@Throws(CryptoStoreErrorException::class)
suspend fun getIdentity(userId: String): UserIdentities? {
val identity = withContext(Dispatchers.IO) {
inner.getIdentity(userId)
}
val adapter = MoshiProvider.providesMoshi().adapter(RestKeyInfo::class.java)
return when (identity) {
is RustUserIdentity.Other -> {
val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel()
val selfSigningKey = adapter.fromJson(identity.selfSigningKey)!!.toCryptoModel()
UserIdentity(identity.userId, masterKey, selfSigningKey, this, this.requestSender)
}
is RustUserIdentity.Own -> {
val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel()
val selfSigningKey = adapter.fromJson(identity.selfSigningKey)!!.toCryptoModel()
val userSigningKey = adapter.fromJson(identity.userSigningKey)!!.toCryptoModel()
OwnUserIdentity(
identity.userId,
masterKey,
selfSigningKey,
userSigningKey,
identity.trustsOurOwnDevice,
this,
this.requestSender
)
}
null -> null
}
}
/**
* Get a `Device` from the store.
*
* This method returns our own device as well.
*
* @param userId The id of the device owner.
*
* @param deviceId The id of the device itself.
@ -437,21 +549,25 @@ internal class OlmMachine(
// using our ownDevice method
ownDevice()
} else {
val device = getRawDevice(userId, deviceId) ?: return null
toCryptoDeviceInfo(device)
getDevice(userId, deviceId)?.toCryptoDeviceInfo()
}
}
private suspend fun getRawDevice(userId: String, deviceId: String): InnerDevice? =
withContext(Dispatchers.IO) {
inner.getDevice(userId, deviceId)
}
@Throws(CryptoStoreErrorException::class)
suspend fun getDevice(userId: String, deviceId: String): Device? {
val device = this.getRawDevice(userId, deviceId) ?: return null
val device = withContext(Dispatchers.IO) {
inner.getDevice(userId, deviceId)
} ?: return null
return Device(this.inner, device, this.requestSender, this.verificationListeners)
}
suspend fun getUserDevices(userId: String): List<Device> {
return withContext(Dispatchers.IO) {
inner.getUserDevices(userId).map { Device(inner, it, requestSender, verificationListeners) }
}
}
/**
* Get all devices of an user.
*
@ -460,36 +576,18 @@ internal class OlmMachine(
* @return The list of Devices or an empty list if there aren't any.
*/
@Throws(CryptoStoreErrorException::class)
suspend fun getUserDevices(userId: String): List<CryptoDeviceInfo> {
val devices = withContext(Dispatchers.IO) {
inner.getUserDevices(userId).map { toCryptoDeviceInfo(it) }.toMutableList()
}
suspend fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
val devices = this.getUserDevices(userId).map { it.toCryptoDeviceInfo() }.toMutableList()
// EA doesn't differentiate much between our own and other devices of
// while the rust-sdk does, append our own device here.
val ownDevice = this.ownDevice()
if (userId == ownDevice.userId) {
devices.add(ownDevice)
if (userId == this.userId()) {
devices.add(this.ownDevice())
}
return devices
}
suspend fun getUserDevicesMap(userIds: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
val userMap = MXUsersDevicesMap<CryptoDeviceInfo>()
for (user in userIds) {
val devices = getUserDevices(user)
for (device in devices) {
userMap.setObject(user, device.deviceId, device)
}
}
return userMap
}
/**
* Get all the devices of multiple users.
*
@ -497,34 +595,45 @@ internal class OlmMachine(
*
* @return The list of Devices or an empty list if there aren't any.
*/
suspend fun getUserDevices(userIds: List<String>): List<CryptoDeviceInfo> {
private suspend fun getCryptoDeviceInfo(userIds: List<String>): List<CryptoDeviceInfo> {
val plainDevices: ArrayList<CryptoDeviceInfo> = arrayListOf()
for (user in userIds) {
val devices = getUserDevices(user)
val devices = this.getCryptoDeviceInfo(user)
plainDevices.addAll(devices)
}
return plainDevices
}
/** Mark the device for the given user with the given device ID as trusted
*
* This will mark the device locally as trusted, it won't upload any type of cross
* signing signature
* */
@Throws(CryptoStoreErrorException::class)
internal suspend fun markDeviceAsTrusted(userId: String, deviceId: String) =
withContext(Dispatchers.IO) {
inner.markDeviceAsTrusted(userId, deviceId)
suspend fun getUserDevicesMap(userIds: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
val userMap = MXUsersDevicesMap<CryptoDeviceInfo>()
for (user in userIds) {
val devices = this.getCryptoDeviceInfo(user)
for (device in devices) {
userMap.setObject(user, device.deviceId, device)
}
}
/** Update all of our live device listeners. */
private suspend fun updateLiveDevices() {
for ((liveDevice, users) in deviceUpdateObserver.listeners) {
val devices = getUserDevices(users)
liveDevice.postValue(devices)
}
return userMap
}
suspend fun getLiveUserIdentity(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
val identity = this.getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
val liveIdentity = LiveUserIdentity(userId, this.userIdentityUpdateObserver)
liveIdentity.value = identity
return liveIdentity
}
suspend fun getLivePrivateCrossSigningKeys(): LiveData<Optional<PrivateKeysInfo>> {
val keys = this.exportCrossSigningKeys().toOptional()
val liveKeys = LivePrivateCrossSigningKeys(this.privateKeysUpdateObserver)
liveKeys.value = keys
return liveKeys
}
/**
@ -538,7 +647,7 @@ internal class OlmMachine(
* @return The list of Devices or an empty list if there aren't any.
*/
suspend fun getLiveDevices(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
val plainDevices = getUserDevices(userIds)
val plainDevices = getCryptoDeviceInfo(userIds)
val devices = LiveDevice(userIds, deviceUpdateObserver)
devices.value = plainDevices
@ -556,7 +665,7 @@ internal class OlmMachine(
* @param userId The ID of the user for which we would like to fetch the
* verification requests
*
* @return The list of VerificationRequests that we share with the given user
* @return The list of [VerificationRequest] that we share with the given user
*/
fun getVerificationRequests(userId: String): List<VerificationRequest> {
return this.inner.getVerificationRequests(userId).map {
@ -585,9 +694,10 @@ internal class OlmMachine(
}
}
/** Get an active verification for the given user and given flow ID
/** Get an active verification for the given user and given flow ID.
*
* This can return a SAS verification or a QR code verification
* @return Either a [SasVerification] verification or a [QrCodeVerification]
* verification.
*/
fun getVerification(userId: String, flowId: String): VerificationTransaction? {
return when (val verification = this.inner.getVerification(userId, flowId)) {
@ -613,4 +723,41 @@ internal class OlmMachine(
}
}
}
suspend fun bootstrapCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
val requests = withContext(Dispatchers.IO) {
inner.bootstrapCrossSigning()
}
this.requestSender.uploadCrossSigningKeys(requests.uploadSigningKeysRequest, uiaInterceptor)
this.requestSender.sendSignatureUpload(requests.signatureRequest)
}
/**
* Get the status of our private cross signing keys, i.e. which private keys do we have stored locally.
*/
fun crossSigningStatus(): CrossSigningStatus {
return this.inner.crossSigningStatus()
}
suspend fun exportCrossSigningKeys(): PrivateKeysInfo? {
val export = withContext(Dispatchers.IO) {
inner.exportCrossSigningKeys()
} ?: return null
return PrivateKeysInfo(export.masterKey, export.selfSigningKey, export.userSigningKey)
}
suspend fun importCrossSigningKeys(export: PrivateKeysInfo): UserTrustResult {
val rustExport = CrossSigningKeyExport(export.master, export.selfSigned, export.user)
withContext(Dispatchers.IO) {
inner.importCrossSigningKeys(rustExport)
}
this.updateLivePrivateKeys()
// TODO map the errors from importCrossSigningKeys to the UserTrustResult
return UserTrustResult.Success
}
}

View File

@ -28,7 +28,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher
import uniffi.olm.CryptoStoreErrorException
import uniffi.olm.OlmMachine
import uniffi.olm.OutgoingVerificationRequest
import uniffi.olm.QrCode
import uniffi.olm.Verification

View File

@ -0,0 +1,209 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto
import androidx.lifecycle.LiveData
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.NoOpMatrixCallback
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustResult
import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult
import org.matrix.android.sdk.internal.crypto.crosssigning.isVerified
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
import org.matrix.android.sdk.internal.extensions.foldToCallback
internal class RustCrossSigningService(private val olmMachine: OlmMachine) : CrossSigningService {
/**
* Is our own device signed by our own cross signing identity
*/
override fun isCrossSigningVerified(): Boolean {
return when (val identity = runBlocking { olmMachine.getIdentity(olmMachine.userId()) }) {
is OwnUserIdentity -> identity.trustsOurOwnDevice()
else -> false
}
}
override fun isUserTrusted(otherUserId: String): Boolean {
// This seems to be used only in tests.
return this.checkUserTrust(otherUserId).isVerified()
}
/**
* Will not force a download of the key, but will verify signatures trust chain.
* Checks that my trusted user key has signed the other user UserKey
*/
override fun checkUserTrust(otherUserId: String): UserTrustResult {
val identity = runBlocking { olmMachine.getIdentity(olmMachine.userId()) }
// While UserTrustResult has many different states, they are by the callers
// converted to a boolean value immediately, thus we don't need to support
// all the values.
return if (identity != null) {
val verified = runBlocking { identity.verified() }
if (verified) {
UserTrustResult.Success
} else {
UserTrustResult.UnknownCrossSignatureInfo(otherUserId)
}
} else {
UserTrustResult.CrossSigningNotConfigured(otherUserId)
}
}
/**
* Initialize cross signing for this user.
* Users needs to enter credentials
*/
override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback<Unit>) {
runBlocking { runCatching { olmMachine.bootstrapCrossSigning(uiaInterceptor) }.foldToCallback(callback) }
}
/**
* Inject the private cross signing keys, likely from backup, into our store.
*
* This will check if the injected private cross signing keys match the public ones provided
* by the server and if they do so
*/
override fun checkTrustFromPrivateKeys(
masterKeyPrivateKey: String?,
uskKeyPrivateKey: String?,
sskPrivateKey: String?
): UserTrustResult {
val export = PrivateKeysInfo(masterKeyPrivateKey, sskPrivateKey, uskKeyPrivateKey)
return runBlocking { olmMachine.importCrossSigningKeys(export) }
}
/**
* Get the public cross signing keys for the given user
*
* @param otherUserId The ID of the user for which we would like to fetch the cross signing keys.
*/
override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
return runBlocking { olmMachine.getIdentity(otherUserId)?.toMxCrossSigningInfo() }
}
override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
return runBlocking { olmMachine.getLiveUserIdentity(userId) }
}
/** Get our own public cross signing keys */
override fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
return getUserCrossSigningKeys(olmMachine.userId())
}
/** Get our own private cross signing keys */
override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
return runBlocking { olmMachine.exportCrossSigningKeys() }
}
override fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> {
return runBlocking { olmMachine.getLivePrivateCrossSigningKeys() }
}
/**
* Can we sign our other devices or other users?
*
* Returning true means that we have the private self-signing and user-signing keys at hand.
*/
override fun canCrossSign(): Boolean {
val status = this.olmMachine.crossSigningStatus()
return status.hasSelfSigning && status.hasUserSigning
}
override fun allPrivateKeysKnown(): Boolean {
val status = this.olmMachine.crossSigningStatus()
return status.hasMaster && status.hasSelfSigning && status.hasUserSigning
}
/** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */
override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) {
// This is only used in a test
val userIdentity = runBlocking { olmMachine.getIdentity(otherUserId) }
if (userIdentity != null) {
runBlocking { userIdentity.verify() }
callback.onSuccess(Unit)
} else {
callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account"))
}
}
/** Mark our own master key as trusted */
override fun markMyMasterKeyAsTrusted() {
// This doesn't seem to be used?
this.trustUser(this.olmMachine.userId(), NoOpMatrixCallback())
}
/**
* Sign one of your devices and upload the signature
*/
override fun trustDevice(deviceId: String, callback: MatrixCallback<Unit>) {
val device = runBlocking { olmMachine.getDevice(olmMachine.userId(), deviceId) }
return if (device != null) {
val verified = runBlocking { device.verify() }
if (verified) {
callback.onSuccess(Unit)
} else {
callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours"))
}
} else {
callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known"))
}
}
/**
* Check if a device is trusted
*
* This will check that we have a valid trust chain from our own master key to a device, either
* using the self-signing key for our own devices or using the user-signing key and the master
* key of another user.
*/
override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
val device = runBlocking { olmMachine.getDevice(otherUserId, otherDeviceId) }
return if (device != null) {
// TODO i don't quite understand the semantics here and there are no docs for
// DeviceTrustResult, what do the different result types mean exactly,
// do you return success only if the Device is cross signing verified?
// what about the local trust if it isn't? why is the local trust passed as an argument here?
DeviceTrustResult.Success(runBlocking { device.trustLevel() })
} else {
DeviceTrustResult.UnknownDevice(otherDeviceId)
}
}
override fun onSecretMSKGossip(mskPrivateKey: String) {
// This seems to be unused.
}
override fun onSecretSSKGossip(sskPrivateKey: String) {
// This as well
}
override fun onSecretUSKGossip(uskPrivateKey: String) {
// And this.
}
}

View File

@ -0,0 +1,256 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
import uniffi.olm.CryptoStoreErrorException
import uniffi.olm.SignatureErrorException
/**
* A sealed class representing user identities.
*
* User identities can come in the form of [OwnUserIdentity] which represents
* our own user identity, or [UserIdentity] which represents a user identity
* belonging to another user.
*/
sealed class UserIdentities {
/**
* The unique ID of the user this identity belongs to.
*/
abstract fun userId(): String
/**
* Check the verification state of the user identity.
*
* @return True if the identity is considered to be verified and trusted, false otherwise.
*/
@Throws(CryptoStoreErrorException::class)
abstract suspend fun verified(): Boolean
/**
* Manually verify the user identity.
*
* This will either sign the identity with our user-signing key if
* it is a identity belonging to another user, or sign the identity
* with our own device.
*
* Throws a SignatureErrorException if we can't sign the identity,
* if for example we don't have access to our user-signing key.
*/
@Throws(SignatureErrorException::class)
abstract suspend fun verify()
/**
* Convert the identity into a MxCrossSigningInfo class.
*/
abstract fun toMxCrossSigningInfo(): MXCrossSigningInfo
}
/**
* A class representing our own user identity.
*
* This is backed by the public parts of our cross signing keys.
**/
internal class OwnUserIdentity(
private val userId: String,
private val masterKey: CryptoCrossSigningKey,
private val selfSigningKey: CryptoCrossSigningKey,
private val userSigningKey: CryptoCrossSigningKey,
private val trustsOurOwnDevice: Boolean,
private val olmMachine: OlmMachine,
private val requestSender: RequestSender) : UserIdentities() {
/**
* Our own user id.
*/
override fun userId() = this.userId
/**
* Manually verify our user identity.
*
* This signs the identity with our own device and upload the signatures to the server.
*
* To perform an interactive verification user the [requestVerification] method instead.
*/
@Throws(SignatureErrorException::class)
override suspend fun verify() {
val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) }
this.requestSender.sendSignatureUpload(request)
}
/**
* Check the verification state of the user identity.
*
* @return True if the identity is considered to be verified and trusted, false otherwise.
*/
@Throws(CryptoStoreErrorException::class)
override suspend fun verified(): Boolean {
return withContext(Dispatchers.IO) { olmMachine.inner().isIdentityVerified(userId) }
}
/**
* Does the identity trust our own device.
*/
fun trustsOurOwnDevice() = this.trustsOurOwnDevice
/**
* Request an interactive verification to begin
*
* This method should be used if we don't have a specific device we want to verify,
* instead we want to send out a verification request to all our devices.
*
* This sends out an `m.key.verification.request` out to all our devices that support E2EE.
* If the identity should be marked as manually verified, use the [verify] method instead.
*
* If a specific device should be verified instead
* the [org.matrix.android.sdk.internal.crypto.Device.requestVerification] method should be
* used instead.
*
* @param methods The list of [VerificationMethod] that we wish to advertise to the other
* side as being supported.
*/
@Throws(CryptoStoreErrorException::class)
suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest {
val stringMethods = prepareMethods(methods)
val result = this.olmMachine.inner().requestSelfVerification(stringMethods)
this.requestSender.sendVerificationRequest(result!!.request)
return VerificationRequest(
this.olmMachine.inner(),
result.verification,
this.requestSender,
this.olmMachine.verificationListeners
)
}
/**
* Convert the identity into a MxCrossSigningInfo class.
*/
override fun toMxCrossSigningInfo(): MXCrossSigningInfo {
val masterKey = this.masterKey
val selfSigningKey = this.selfSigningKey
val userSigningKey = this.userSigningKey
val trustLevel = DeviceTrustLevel(runBlocking { verified() }, false)
// TODO remove this, this is silly, we have way too many methods to check if a user is verified
masterKey.trustLevel = trustLevel
selfSigningKey.trustLevel = trustLevel
userSigningKey.trustLevel = trustLevel
val crossSigningKeys = listOf(masterKey, selfSigningKey, userSigningKey)
return MXCrossSigningInfo(this.userId, crossSigningKeys)
}
}
/**
* A class representing another users identity.
*
* This is backed by the public parts of the users cross signing keys.
**/
internal class UserIdentity(
private val userId: String,
private val masterKey: CryptoCrossSigningKey,
private val selfSigningKey: CryptoCrossSigningKey,
private val olmMachine: OlmMachine,
private val requestSender: RequestSender) : UserIdentities() {
/**
* The unique ID of the user that this identity belongs to.
*/
override fun userId() = this.userId
/**
* Manually verify this user identity.
*
* This signs the identity with our user-signing key.
*
* This method can fail if we don't have the private part of our user-signing key at hand.
*
* To perform an interactive verification user the [requestVerification] method instead.
*/
@Throws(SignatureErrorException::class)
override suspend fun verify() {
val request = withContext(Dispatchers.Default) { olmMachine.inner().verifyIdentity(userId) }
this.requestSender.sendSignatureUpload(request)
}
/**
* Check the verification state of the user identity.
*
* @return True if the identity is considered to be verified and trusted, false otherwise.
*/
override suspend fun verified(): Boolean {
return withContext(Dispatchers.IO) { olmMachine.inner().isIdentityVerified(userId) }
}
/**
* Request an interactive verification to begin.
*
* This method should be used if we don't have a specific device we want to verify,
* instead we want to send out a verification request to all our devices. For user
* identities that aren't our own, this method should be the primary way to verify users
* and their devices.
*
* This sends out an `m.key.verification.request` out to the room with the given room ID.
* The room **must** be a private DM that we share with this user.
*
* If the identity should be marked as manually verified, use the [verify] method instead.
*
* If a specific device should be verified instead
* the [org.matrix.android.sdk.internal.crypto.Device.requestVerification] method should be
* used instead.
*
* @param methods The list of [VerificationMethod] that we wish to advertise to the other
* side as being supported.
* @param roomId The ID of the room which represents a DM that we share with this user.
* @param transactionId The transaction id that should be used for the request that sends
* the `m.key.verification.request` to the room.
*/
@Throws(CryptoStoreErrorException::class)
suspend fun requestVerification(
methods: List<VerificationMethod>,
roomId: String,
transactionId: String
): VerificationRequest {
val stringMethods = prepareMethods(methods)
val content = this.olmMachine.inner().verificationRequestContent(this.userId, stringMethods)!!
val eventID = requestSender.sendRoomMessage(EventType.MESSAGE, roomId, content, transactionId)
val innerRequest = this.olmMachine.inner().requestVerification(this.userId, roomId, eventID, stringMethods)!!
return VerificationRequest(
this.olmMachine.inner(),
innerRequest,
this.requestSender,
this.olmMachine.verificationListeners
)
}
/**
* Convert the identity into a MxCrossSigningInfo class.
*/
override fun toMxCrossSigningInfo(): MXCrossSigningInfo {
val crossSigningKeys = listOf(this.masterKey, this.selfSigningKey)
return MXCrossSigningInfo(this.userId, crossSigningKeys)
}
}

View File

@ -31,9 +31,9 @@ import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.internal.crypto.OlmMachine
import org.matrix.android.sdk.internal.crypto.RequestSender
import org.matrix.android.sdk.internal.crypto.OwnUserIdentity
import org.matrix.android.sdk.internal.crypto.SasVerification
import org.matrix.android.sdk.internal.crypto.VerificationRequest
import org.matrix.android.sdk.internal.crypto.UserIdentity
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
@ -124,10 +124,7 @@ internal class UpdateDispatcher(private val listeners: ArrayList<VerificationSer
}
}
internal class RustVerificationService(
private val olmMachine: OlmMachine,
private val requestSender: RequestSender,
) : VerificationService {
internal class RustVerificationService(private val olmMachine: OlmMachine) : VerificationService {
private val dispatcher = UpdateDispatcher(this.olmMachine.verificationListeners)
/** The main entry point for the verification service
@ -280,19 +277,13 @@ internal class RustVerificationService(
otherUserId: String,
otherDevices: List<String>?
): PendingVerificationRequest {
val stringMethods = prepareMethods(methods)
val result = this.olmMachine.inner().requestSelfVerification(stringMethods)
runBlocking {
requestSender.sendVerificationRequest(result!!.request)
val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) {
is OwnUserIdentity -> runBlocking { identity.requestVerification(methods) }
is UserIdentity -> throw IllegalArgumentException("This method doesn't support verification of other users devices")
null -> throw IllegalArgumentException("Cross signing has not been bootstrapped for our own user")
}
return VerificationRequest(
this.olmMachine.inner(),
result!!.verification,
this.requestSender,
this.olmMachine.verificationListeners
).toPendingVerificationRequest()
return verification.toPendingVerificationRequest()
}
override fun requestKeyVerificationInDMs(
@ -302,20 +293,13 @@ internal class RustVerificationService(
localId: String?
): PendingVerificationRequest {
Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId")
val stringMethods = prepareMethods(methods)
val content = this.olmMachine.inner().verificationRequestContent(otherUserId, stringMethods)!!
val eventID = runBlocking {
requestSender.sendRoomMessage(EventType.MESSAGE, roomId, content, localId!!)
val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) {
is UserIdentity -> runBlocking { identity.requestVerification(methods, roomId, localId!!) }
is OwnUserIdentity -> throw IllegalArgumentException("This method doesn't support verification of our own user")
null -> throw IllegalArgumentException("The user that we wish to verify doesn't support cross signing")
}
val innerRequest = this.olmMachine.inner().requestVerification(otherUserId, roomId, eventID, stringMethods)!!
return VerificationRequest(
this.olmMachine.inner(),
innerRequest,
this.requestSender,
this.olmMachine.verificationListeners
).toPendingVerificationRequest()
return verification.toPendingVerificationRequest()
}
override fun readyPendingVerification(