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.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()
} }

View File

@ -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)
}
} }

View File

@ -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
}
} }

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 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

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.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(