crypto: Add a Rust based CrossSigningService
This commit is contained in:
parent
b012a0ff75
commit
c85847df57
@ -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.listeners.ProgressListener
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
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.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.keyshare.GossipingRequestListener
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
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.RoomHistoryVisibilityContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
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.crosssigning.DeviceTrustLevel
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
||||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
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.KeysClaimResponse
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
|
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.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.repository.WarnOnUnknownDeviceRepository
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
|
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.SendVerificationMessageTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
|
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.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.crypto.verification.RustVerificationService
|
||||||
import org.matrix.android.sdk.internal.di.DeviceId
|
import org.matrix.android.sdk.internal.di.DeviceId
|
||||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||||
@ -98,6 +102,8 @@ import timber.log.Timber
|
|||||||
import uniffi.olm.OutgoingVerificationRequest
|
import uniffi.olm.OutgoingVerificationRequest
|
||||||
import uniffi.olm.Request
|
import uniffi.olm.Request
|
||||||
import uniffi.olm.RequestType
|
import uniffi.olm.RequestType
|
||||||
|
import uniffi.olm.SignatureUploadRequest
|
||||||
|
import uniffi.olm.UploadSigningKeysRequest
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
@ -109,7 +115,9 @@ internal class RequestSender @Inject constructor(
|
|||||||
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask,
|
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask,
|
||||||
private val uploadKeysTask: UploadKeysTask,
|
private val uploadKeysTask: UploadKeysTask,
|
||||||
private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
|
private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
|
||||||
|
private val signaturesUploadTask: UploadSignaturesTask,
|
||||||
private val sendVerificationMessageTask: Lazy<DefaultSendVerificationMessageTask>,
|
private val sendVerificationMessageTask: Lazy<DefaultSendVerificationMessageTask>,
|
||||||
|
private val uploadSigningKeysTask: UploadSigningKeysTask,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun claimKeys(request: Request.KeysClaim): String {
|
suspend fun claimKeys(request: Request.KeysClaim): String {
|
||||||
@ -161,6 +169,55 @@ internal class RequestSender @Inject constructor(
|
|||||||
return this.sendVerificationMessageTask.get().execute(params)
|
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) {
|
suspend fun sendToDevice(request: Request.ToDevice) {
|
||||||
sendToDevice(request.eventType, request.body, request.requestId)
|
sendToDevice(request.eventType, request.body, request.requestId)
|
||||||
}
|
}
|
||||||
@ -208,7 +265,6 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
private val mxCryptoConfig: MXCryptoConfig,
|
private val mxCryptoConfig: MXCryptoConfig,
|
||||||
// The key backup service.
|
// The key backup service.
|
||||||
private val keysBackupService: DefaultKeysBackupService,
|
private val keysBackupService: DefaultKeysBackupService,
|
||||||
private val crossSigningService: DefaultCrossSigningService,
|
|
||||||
// Actions
|
// Actions
|
||||||
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
|
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
|
||||||
// Tasks
|
// Tasks
|
||||||
@ -232,6 +288,9 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
// The verification service.
|
// The verification service.
|
||||||
private var verificationService: RustVerificationService? = null
|
private var verificationService: RustVerificationService? = null
|
||||||
|
|
||||||
|
// The cross signing service.
|
||||||
|
private var crossSigningService: RustCrossSigningService? = null
|
||||||
|
|
||||||
private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver()
|
private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver()
|
||||||
|
|
||||||
// Locks for some of our operations
|
// 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"
|
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>) {
|
override fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>) {
|
||||||
getDevicesTask
|
getDevicesTask
|
||||||
@ -394,7 +455,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
setRustLogger()
|
setRustLogger()
|
||||||
val machine = OlmMachine(userId, deviceId!!, dataDir, deviceObserver, sender)
|
val machine = OlmMachine(userId, deviceId!!, dataDir, deviceObserver, sender)
|
||||||
olmMachine = machine
|
olmMachine = machine
|
||||||
verificationService = RustVerificationService(machine, this.sender)
|
verificationService = RustVerificationService(machine)
|
||||||
|
crossSigningService = RustCrossSigningService(machine)
|
||||||
Timber.v(
|
Timber.v(
|
||||||
"## CRYPTO | Successfully started up an Olm machine for " +
|
"## CRYPTO | Successfully started up an Olm machine for " +
|
||||||
"${userId}, ${deviceId}, identity keys: ${this.olmMachine?.identityKeys()}")
|
"${userId}, ${deviceId}, identity keys: ${this.olmMachine?.identityKeys()}")
|
||||||
@ -453,7 +515,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
return verificationService!!
|
return verificationService!!
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun crossSigningService() = crossSigningService
|
override fun crossSigningService(): CrossSigningService {
|
||||||
|
if (crossSigningService == null) {
|
||||||
|
internalStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
return crossSigningService!!
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A sync response has been received
|
* A sync response has been received
|
||||||
@ -461,6 +529,15 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
suspend fun onSyncCompleted() {
|
suspend fun onSyncCompleted() {
|
||||||
if (isStarted()) {
|
if (isStarted()) {
|
||||||
sendOutgoingRequests()
|
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) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
@ -491,7 +568,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
|
|
||||||
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||||
return runBlocking {
|
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.
|
* @return the stored device keys for a user.
|
||||||
*/
|
*/
|
||||||
override fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
|
override fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
|
||||||
return cryptoStore.getUserDevices(userId)?.values?.toMutableList() ?: ArrayList()
|
return this.getCryptoDeviceInfo(userId).toMutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isEncryptionEnabledForInvitedUser(): Boolean {
|
private fun isEncryptionEnabledForInvitedUser(): Boolean {
|
||||||
@ -845,6 +922,11 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
olmMachine!!.markRequestAsSent(request.requestId, RequestType.KEYS_CLAIM, response)
|
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() {
|
private suspend fun sendOutgoingRequests() {
|
||||||
outgoingRequestsLock.withLock {
|
outgoingRequestsLock.withLock {
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
@ -875,6 +957,11 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
sender.sendRoomMessage(it)
|
sender.sendRoomMessage(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is Request.SignatureUpload -> {
|
||||||
|
async {
|
||||||
|
signatureUpload(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}.joinAll()
|
}.joinAll()
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,14 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
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.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 org.matrix.android.sdk.internal.crypto.verification.prepareMethods
|
||||||
import uniffi.olm.CryptoStoreErrorException
|
import uniffi.olm.CryptoStoreErrorException
|
||||||
import uniffi.olm.Device as InnerDevice
|
import uniffi.olm.Device as InnerDevice
|
||||||
import uniffi.olm.OlmMachine
|
import uniffi.olm.OlmMachine
|
||||||
|
import uniffi.olm.SignatureErrorException
|
||||||
import uniffi.olm.VerificationRequest
|
import uniffi.olm.VerificationRequest
|
||||||
|
|
||||||
/** Class representing a device that supports E2EE in the Matrix world
|
/** Class representing a device that supports E2EE in the Matrix world
|
||||||
@ -37,10 +41,27 @@ internal class Device(
|
|||||||
private val sender: RequestSender,
|
private val sender: RequestSender,
|
||||||
private val listeners: ArrayList<VerificationService.Listener>,
|
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
|
* This sends out a m.key.verification.request event over to-device messaging to
|
||||||
* to this device.
|
* 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)
|
@Throws(CryptoStoreErrorException::class)
|
||||||
suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest? {
|
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
|
* This sends out a m.key.verification.start event with the method set to
|
||||||
* m.sas.v1 to this device using to-device messaging.
|
* 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)
|
@Throws(CryptoStoreErrorException::class)
|
||||||
suspend fun startVerification(): SasVerification? {
|
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
|
* This won't upload any signatures, it will only mark the device as trusted
|
||||||
* in the local database.
|
* in the local database.
|
||||||
@ -89,4 +115,56 @@ internal class Device(
|
|||||||
machine.markDeviceAsTrusted(inner.userId, inner.deviceId)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,26 +24,34 @@ import java.util.concurrent.ConcurrentHashMap
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
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.listeners.ProgressListener
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
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.VerificationService
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
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.Content
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
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.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.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.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
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.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.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.di.MoshiProvider
|
||||||
import org.matrix.android.sdk.internal.session.sync.model.DeviceListResponse
|
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.DeviceOneTimeKeysCountSyncResponse
|
||||||
import org.matrix.android.sdk.internal.session.sync.model.ToDeviceSyncResponse
|
import org.matrix.android.sdk.internal.session.sync.model.ToDeviceSyncResponse
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import uniffi.olm.CrossSigningKeyExport
|
||||||
|
import uniffi.olm.CrossSigningStatus
|
||||||
import uniffi.olm.CryptoStoreErrorException
|
import uniffi.olm.CryptoStoreErrorException
|
||||||
import uniffi.olm.DecryptionErrorException
|
import uniffi.olm.DecryptionErrorException
|
||||||
import uniffi.olm.Device as InnerDevice
|
|
||||||
import uniffi.olm.DeviceLists
|
import uniffi.olm.DeviceLists
|
||||||
import uniffi.olm.KeyRequestPair
|
import uniffi.olm.KeyRequestPair
|
||||||
import uniffi.olm.Logger
|
import uniffi.olm.Logger
|
||||||
@ -51,6 +59,7 @@ import uniffi.olm.OlmMachine as InnerMachine
|
|||||||
import uniffi.olm.ProgressListener as RustProgressListener
|
import uniffi.olm.ProgressListener as RustProgressListener
|
||||||
import uniffi.olm.Request
|
import uniffi.olm.Request
|
||||||
import uniffi.olm.RequestType
|
import uniffi.olm.RequestType
|
||||||
|
import uniffi.olm.UserIdentity as RustUserIdentity
|
||||||
import uniffi.olm.setLogger
|
import uniffi.olm.setLogger
|
||||||
|
|
||||||
class CryptoLogger : Logger {
|
class CryptoLogger : Logger {
|
||||||
@ -83,27 +92,34 @@ internal class LiveDevice(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setRustLogger() {
|
internal class LiveUserIdentity(
|
||||||
setLogger(CryptoLogger() as Logger)
|
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 */
|
internal class LivePrivateCrossSigningKeys(
|
||||||
private fun toCryptoDeviceInfo(device: InnerDevice): CryptoDeviceInfo {
|
private var observer: PrivateCrossSigningKeysUpdateObserver,
|
||||||
val keys = device.keys.map { (keyId, key) -> "$keyId:$device.deviceId" to key }.toMap()
|
) : MutableLiveData<Optional<PrivateKeysInfo>>() {
|
||||||
|
|
||||||
return CryptoDeviceInfo(
|
override fun onActive() {
|
||||||
device.deviceId,
|
observer.addUserIdentityUpdateListener(this)
|
||||||
device.userId,
|
}
|
||||||
device.algorithms,
|
|
||||||
keys,
|
override fun onInactive() {
|
||||||
// TODO pass the signatures here, do we need this, why should the
|
observer.removeUserIdentityUpdateListener(this)
|
||||||
// Kotlin side care about signatures?
|
}
|
||||||
mapOf(),
|
}
|
||||||
UnsignedDeviceInfo(device.displayName),
|
|
||||||
DeviceTrustLevel(crossSigningVerified = device.crossSigningTrusted, locallyVerified = device.locallyTrusted),
|
fun setRustLogger() {
|
||||||
device.isBlocked,
|
setLogger(CryptoLogger() as Logger)
|
||||||
// TODO
|
|
||||||
null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DeviceUpdateObserver {
|
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(
|
internal class OlmMachine(
|
||||||
user_id: String,
|
user_id: String,
|
||||||
device_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 inner: InnerMachine = InnerMachine(user_id, device_id, path.toString())
|
||||||
private val deviceUpdateObserver = deviceObserver
|
private val deviceUpdateObserver = deviceObserver
|
||||||
|
private val userIdentityUpdateObserver = UserIdentityUpdateObserver()
|
||||||
|
private val privateKeysUpdateObserver = PrivateCrossSigningKeysUpdateObserver()
|
||||||
internal val verificationListeners = ArrayList<VerificationService.Listener>()
|
internal val verificationListeners = ArrayList<VerificationService.Listener>()
|
||||||
|
|
||||||
/** Get our own user ID. */
|
/** Get our own user ID. */
|
||||||
@ -148,11 +190,42 @@ internal class OlmMachine(
|
|||||||
return this.inner
|
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 deviceId = this.deviceId()
|
||||||
|
|
||||||
val keys = this.identityKeys().map { (keyId, key) -> "$keyId:$deviceId" to key }.toMap()
|
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(
|
return CryptoDeviceInfo(
|
||||||
this.deviceId(),
|
this.deviceId(),
|
||||||
this.userId(),
|
this.userId(),
|
||||||
@ -161,7 +234,7 @@ internal class OlmMachine(
|
|||||||
keys,
|
keys,
|
||||||
mapOf(),
|
mapOf(),
|
||||||
UnsignedDeviceInfo(),
|
UnsignedDeviceInfo(),
|
||||||
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
DeviceTrustLevel(crossSigningVerified, locallyVerified = true),
|
||||||
false,
|
false,
|
||||||
null)
|
null)
|
||||||
}
|
}
|
||||||
@ -170,7 +243,7 @@ internal class OlmMachine(
|
|||||||
* Get the list of outgoing requests that need to be sent to the homeserver.
|
* 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
|
* 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
|
* @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) {
|
if (requestType == RequestType.KEYS_QUERY) {
|
||||||
updateLiveDevices()
|
updateLiveDevices()
|
||||||
|
updateLiveUserIdentities()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,8 +292,8 @@ internal class OlmMachine(
|
|||||||
toDevice: ToDeviceSyncResponse?,
|
toDevice: ToDeviceSyncResponse?,
|
||||||
deviceChanges: DeviceListResponse?,
|
deviceChanges: DeviceListResponse?,
|
||||||
keyCounts: DeviceOneTimeKeysCountSyncResponse?
|
keyCounts: DeviceOneTimeKeysCountSyncResponse?
|
||||||
): ToDeviceSyncResponse =
|
): ToDeviceSyncResponse {
|
||||||
withContext(Dispatchers.IO) {
|
val response = withContext(Dispatchers.IO) {
|
||||||
val counts: MutableMap<String, Int> = mutableMapOf()
|
val counts: MutableMap<String, Int> = mutableMapOf()
|
||||||
|
|
||||||
if (keyCounts?.signedCurve25519 != null) {
|
if (keyCounts?.signedCurve25519 != null) {
|
||||||
@ -236,6 +310,12 @@ internal class OlmMachine(
|
|||||||
adapter.fromJson(inner.receiveSyncChanges(events, devices, counts))!!
|
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.
|
* 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.
|
* 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
|
* 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.
|
* @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)
|
@Throws(CryptoStoreErrorException::class)
|
||||||
suspend fun getMissingSessions(users: List<String>): Request? =
|
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
|
* @param users The list of users which are considered to be members of the room and should
|
||||||
* receive the room key.
|
* 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)
|
@Throws(CryptoStoreErrorException::class)
|
||||||
suspend fun shareRoomKey(roomId: String, users: List<String>): List<Request> =
|
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.
|
* 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
|
* **Note**: A room key needs to be shared with the group of users that are members
|
||||||
* room. If this is not done this method will panic.
|
* 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:
|
* 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
|
* 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
|
* the room members of the room we wish to participate in. This is done
|
||||||
* using the [`get_missing_sessions()`](#method.get_missing_sessions)
|
* using the [getMissingSessions] method. This method call should be locked per call.
|
||||||
* method. This method call should be locked per call.
|
*
|
||||||
* ```
|
* 2. Share a room key with all the room members using the [shareRoomKey].
|
||||||
* 2. Share a room key with all the room members using the shareRoomKey().
|
|
||||||
* ```
|
|
||||||
* This method call should be locked per room.
|
* This method call should be locked per room.
|
||||||
* ```
|
*
|
||||||
* 3. Encrypt the event using this method.
|
* 3. Encrypt the event using this method.
|
||||||
*
|
*
|
||||||
* 4. Send the encrypted event to the server.
|
* 4. Send the encrypted event to the server.
|
||||||
@ -316,7 +393,7 @@ internal class OlmMachine(
|
|||||||
*
|
*
|
||||||
* @param content the JSON content of the event
|
* @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)
|
@Throws(CryptoStoreErrorException::class)
|
||||||
suspend fun encrypt(roomId: String, eventType: String, content: Content): Content =
|
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.
|
* @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)
|
@Throws(MXCryptoError::class)
|
||||||
suspend fun decryptRoomEvent(event: Event): MXEventDecryptionResult =
|
suspend fun decryptRoomEvent(event: Event): MXEventDecryptionResult =
|
||||||
@ -421,9 +498,44 @@ internal class OlmMachine(
|
|||||||
ImportRoomKeysResult(result.total, result.imported)
|
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.
|
* Get a `Device` from the store.
|
||||||
*
|
*
|
||||||
|
* This method returns our own device as well.
|
||||||
|
*
|
||||||
* @param userId The id of the device owner.
|
* @param userId The id of the device owner.
|
||||||
*
|
*
|
||||||
* @param deviceId The id of the device itself.
|
* @param deviceId The id of the device itself.
|
||||||
@ -437,21 +549,25 @@ internal class OlmMachine(
|
|||||||
// using our ownDevice method
|
// using our ownDevice method
|
||||||
ownDevice()
|
ownDevice()
|
||||||
} else {
|
} else {
|
||||||
val device = getRawDevice(userId, deviceId) ?: return null
|
getDevice(userId, deviceId)?.toCryptoDeviceInfo()
|
||||||
toCryptoDeviceInfo(device)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getRawDevice(userId: String, deviceId: String): InnerDevice? =
|
@Throws(CryptoStoreErrorException::class)
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
inner.getDevice(userId, deviceId)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getDevice(userId: String, deviceId: String): Device? {
|
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)
|
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.
|
* 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.
|
* @return The list of Devices or an empty list if there aren't any.
|
||||||
*/
|
*/
|
||||||
@Throws(CryptoStoreErrorException::class)
|
@Throws(CryptoStoreErrorException::class)
|
||||||
suspend fun getUserDevices(userId: String): List<CryptoDeviceInfo> {
|
suspend fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||||
val devices = withContext(Dispatchers.IO) {
|
val devices = this.getUserDevices(userId).map { it.toCryptoDeviceInfo() }.toMutableList()
|
||||||
inner.getUserDevices(userId).map { toCryptoDeviceInfo(it) }.toMutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// EA doesn't differentiate much between our own and other devices of
|
// EA doesn't differentiate much between our own and other devices of
|
||||||
// while the rust-sdk does, append our own device here.
|
// while the rust-sdk does, append our own device here.
|
||||||
val ownDevice = this.ownDevice()
|
if (userId == this.userId()) {
|
||||||
|
devices.add(this.ownDevice())
|
||||||
if (userId == ownDevice.userId) {
|
|
||||||
devices.add(ownDevice)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return devices
|
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.
|
* 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.
|
* @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()
|
val plainDevices: ArrayList<CryptoDeviceInfo> = arrayListOf()
|
||||||
|
|
||||||
for (user in userIds) {
|
for (user in userIds) {
|
||||||
val devices = getUserDevices(user)
|
val devices = this.getCryptoDeviceInfo(user)
|
||||||
plainDevices.addAll(devices)
|
plainDevices.addAll(devices)
|
||||||
}
|
}
|
||||||
|
|
||||||
return plainDevices
|
return plainDevices
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Mark the device for the given user with the given device ID as trusted
|
suspend fun getUserDevicesMap(userIds: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
|
||||||
*
|
val userMap = MXUsersDevicesMap<CryptoDeviceInfo>()
|
||||||
* This will mark the device locally as trusted, it won't upload any type of cross
|
|
||||||
* signing signature
|
for (user in userIds) {
|
||||||
* */
|
val devices = this.getCryptoDeviceInfo(user)
|
||||||
@Throws(CryptoStoreErrorException::class)
|
|
||||||
internal suspend fun markDeviceAsTrusted(userId: String, deviceId: String) =
|
for (device in devices) {
|
||||||
withContext(Dispatchers.IO) {
|
userMap.setObject(user, device.deviceId, device)
|
||||||
inner.markDeviceAsTrusted(userId, deviceId)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update all of our live device listeners. */
|
return userMap
|
||||||
private suspend fun updateLiveDevices() {
|
|
||||||
for ((liveDevice, users) in deviceUpdateObserver.listeners) {
|
|
||||||
val devices = getUserDevices(users)
|
|
||||||
liveDevice.postValue(devices)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
* @return The list of Devices or an empty list if there aren't any.
|
||||||
*/
|
*/
|
||||||
suspend fun getLiveDevices(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
|
suspend fun getLiveDevices(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
|
||||||
val plainDevices = getUserDevices(userIds)
|
val plainDevices = getCryptoDeviceInfo(userIds)
|
||||||
val devices = LiveDevice(userIds, deviceUpdateObserver)
|
val devices = LiveDevice(userIds, deviceUpdateObserver)
|
||||||
devices.value = plainDevices
|
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
|
* @param userId The ID of the user for which we would like to fetch the
|
||||||
* verification requests
|
* 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> {
|
fun getVerificationRequests(userId: String): List<VerificationRequest> {
|
||||||
return this.inner.getVerificationRequests(userId).map {
|
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? {
|
fun getVerification(userId: String, flowId: String): VerificationTransaction? {
|
||||||
return when (val verification = this.inner.getVerification(userId, flowId)) {
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
|||||||
import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher
|
import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher
|
||||||
import uniffi.olm.CryptoStoreErrorException
|
import uniffi.olm.CryptoStoreErrorException
|
||||||
import uniffi.olm.OlmMachine
|
import uniffi.olm.OlmMachine
|
||||||
import uniffi.olm.OutgoingVerificationRequest
|
|
||||||
import uniffi.olm.QrCode
|
import uniffi.olm.QrCode
|
||||||
import uniffi.olm.Verification
|
import uniffi.olm.Verification
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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.MessageRelationContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
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.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.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_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_QR_CODE_SHOW
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
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(
|
internal class RustVerificationService(private val olmMachine: OlmMachine) : VerificationService {
|
||||||
private val olmMachine: OlmMachine,
|
|
||||||
private val requestSender: RequestSender,
|
|
||||||
) : VerificationService {
|
|
||||||
private val dispatcher = UpdateDispatcher(this.olmMachine.verificationListeners)
|
private val dispatcher = UpdateDispatcher(this.olmMachine.verificationListeners)
|
||||||
|
|
||||||
/** The main entry point for the verification service
|
/** The main entry point for the verification service
|
||||||
@ -280,19 +277,13 @@ internal class RustVerificationService(
|
|||||||
otherUserId: String,
|
otherUserId: String,
|
||||||
otherDevices: List<String>?
|
otherDevices: List<String>?
|
||||||
): PendingVerificationRequest {
|
): PendingVerificationRequest {
|
||||||
val stringMethods = prepareMethods(methods)
|
val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) {
|
||||||
|
is OwnUserIdentity -> runBlocking { identity.requestVerification(methods) }
|
||||||
val result = this.olmMachine.inner().requestSelfVerification(stringMethods)
|
is UserIdentity -> throw IllegalArgumentException("This method doesn't support verification of other users devices")
|
||||||
runBlocking {
|
null -> throw IllegalArgumentException("Cross signing has not been bootstrapped for our own user")
|
||||||
requestSender.sendVerificationRequest(result!!.request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return VerificationRequest(
|
return verification.toPendingVerificationRequest()
|
||||||
this.olmMachine.inner(),
|
|
||||||
result!!.verification,
|
|
||||||
this.requestSender,
|
|
||||||
this.olmMachine.verificationListeners
|
|
||||||
).toPendingVerificationRequest()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requestKeyVerificationInDMs(
|
override fun requestKeyVerificationInDMs(
|
||||||
@ -302,20 +293,13 @@ internal class RustVerificationService(
|
|||||||
localId: String?
|
localId: String?
|
||||||
): PendingVerificationRequest {
|
): PendingVerificationRequest {
|
||||||
Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId")
|
Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId")
|
||||||
val stringMethods = prepareMethods(methods)
|
val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) {
|
||||||
val content = this.olmMachine.inner().verificationRequestContent(otherUserId, stringMethods)!!
|
is UserIdentity -> runBlocking { identity.requestVerification(methods, roomId, localId!!) }
|
||||||
|
is OwnUserIdentity -> throw IllegalArgumentException("This method doesn't support verification of our own user")
|
||||||
val eventID = runBlocking {
|
null -> throw IllegalArgumentException("The user that we wish to verify doesn't support cross signing")
|
||||||
requestSender.sendRoomMessage(EventType.MESSAGE, roomId, content, localId!!)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val innerRequest = this.olmMachine.inner().requestVerification(otherUserId, roomId, eventID, stringMethods)!!
|
return verification.toPendingVerificationRequest()
|
||||||
return VerificationRequest(
|
|
||||||
this.olmMachine.inner(),
|
|
||||||
innerRequest,
|
|
||||||
this.requestSender,
|
|
||||||
this.olmMachine.verificationListeners
|
|
||||||
).toPendingVerificationRequest()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readyPendingVerification(
|
override fun readyPendingVerification(
|
||||||
|
Loading…
Reference in New Issue
Block a user