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.session.crypto.CryptoService
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
||||
import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
|
@ -51,7 +52,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
|||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService
|
||||
import org.matrix.android.sdk.internal.auth.registration.handleUIA
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||
|
@ -66,6 +67,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.ForwardedRoomKeyContent
|
|||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
|
||||
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
|
||||
|
@ -78,6 +80,8 @@ import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
|||
import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask
|
||||
import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask
|
||||
import org.matrix.android.sdk.internal.crypto.verification.RustVerificationService
|
||||
import org.matrix.android.sdk.internal.di.DeviceId
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
|
@ -98,6 +102,8 @@ import timber.log.Timber
|
|||
import uniffi.olm.OutgoingVerificationRequest
|
||||
import uniffi.olm.Request
|
||||
import uniffi.olm.RequestType
|
||||
import uniffi.olm.SignatureUploadRequest
|
||||
import uniffi.olm.UploadSigningKeysRequest
|
||||
import java.io.File
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
@ -109,7 +115,9 @@ internal class RequestSender @Inject constructor(
|
|||
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask,
|
||||
private val uploadKeysTask: UploadKeysTask,
|
||||
private val downloadKeysForUsersTask: DownloadKeysForUsersTask,
|
||||
private val signaturesUploadTask: UploadSignaturesTask,
|
||||
private val sendVerificationMessageTask: Lazy<DefaultSendVerificationMessageTask>,
|
||||
private val uploadSigningKeysTask: UploadSigningKeysTask,
|
||||
) {
|
||||
|
||||
suspend fun claimKeys(request: Request.KeysClaim): String {
|
||||
|
@ -161,6 +169,55 @@ internal class RequestSender @Inject constructor(
|
|||
return this.sendVerificationMessageTask.get().execute(params)
|
||||
}
|
||||
|
||||
suspend fun sendSignatureUpload(request: Request.SignatureUpload) {
|
||||
sendSignatureUpload(request.body)
|
||||
}
|
||||
|
||||
suspend fun sendSignatureUpload(request: SignatureUploadRequest) {
|
||||
sendSignatureUpload(request.body)
|
||||
}
|
||||
|
||||
private suspend fun sendSignatureUpload(body: String) {
|
||||
val adapter = MoshiProvider.providesMoshi().adapter<Map<String, Map<String, Any>>>(Map::class.java)
|
||||
val signatures = adapter.fromJson(body)!!
|
||||
val params = UploadSignaturesTask.Params(signatures)
|
||||
this.signaturesUploadTask.execute(params)
|
||||
}
|
||||
|
||||
suspend fun uploadCrossSigningKeys(
|
||||
request: UploadSigningKeysRequest,
|
||||
interactiveAuthInterceptor: UserInteractiveAuthInterceptor?
|
||||
) {
|
||||
val adapter = MoshiProvider.providesMoshi().adapter<RestKeyInfo>(RestKeyInfo::class.java)
|
||||
val masterKey = adapter.fromJson(request.masterKey)!!.toCryptoModel()
|
||||
val selfSigningKey = adapter.fromJson(request.selfSigningKey)!!.toCryptoModel()
|
||||
val userSigningKey = adapter.fromJson(request.userSigningKey)!!.toCryptoModel()
|
||||
|
||||
val uploadSigningKeysParams = UploadSigningKeysTask.Params(
|
||||
masterKey,
|
||||
userSigningKey,
|
||||
selfSigningKey,
|
||||
null
|
||||
)
|
||||
|
||||
try {
|
||||
uploadSigningKeysTask.execute(uploadSigningKeysParams)
|
||||
} catch (failure: Throwable) {
|
||||
if (interactiveAuthInterceptor == null
|
||||
|| !handleUIA(
|
||||
failure = failure,
|
||||
interceptor = interactiveAuthInterceptor,
|
||||
retryBlock = { authUpdate ->
|
||||
uploadSigningKeysTask.execute(uploadSigningKeysParams.copy(userAuthParam = authUpdate))
|
||||
}
|
||||
)
|
||||
) {
|
||||
Timber.d("## UIA: propagate failure")
|
||||
throw failure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun sendToDevice(request: Request.ToDevice) {
|
||||
sendToDevice(request.eventType, request.body, request.requestId)
|
||||
}
|
||||
|
@ -208,7 +265,6 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
private val mxCryptoConfig: MXCryptoConfig,
|
||||
// The key backup service.
|
||||
private val keysBackupService: DefaultKeysBackupService,
|
||||
private val crossSigningService: DefaultCrossSigningService,
|
||||
// Actions
|
||||
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
|
||||
// Tasks
|
||||
|
@ -232,6 +288,9 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
// The verification service.
|
||||
private var verificationService: RustVerificationService? = null
|
||||
|
||||
// The cross signing service.
|
||||
private var crossSigningService: RustCrossSigningService? = null
|
||||
|
||||
private val deviceObserver: DeviceUpdateObserver = DeviceUpdateObserver()
|
||||
|
||||
// Locks for some of our operations
|
||||
|
@ -294,7 +353,9 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
return if (longFormat) "Rust SDK 0.3" else "0.3"
|
||||
}
|
||||
|
||||
override fun getMyDevice(): CryptoDeviceInfo = this.olmMachine!!.ownDevice()
|
||||
override fun getMyDevice(): CryptoDeviceInfo {
|
||||
return runBlocking { olmMachine!!.ownDevice() }
|
||||
}
|
||||
|
||||
override fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>) {
|
||||
getDevicesTask
|
||||
|
@ -394,7 +455,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
setRustLogger()
|
||||
val machine = OlmMachine(userId, deviceId!!, dataDir, deviceObserver, sender)
|
||||
olmMachine = machine
|
||||
verificationService = RustVerificationService(machine, this.sender)
|
||||
verificationService = RustVerificationService(machine)
|
||||
crossSigningService = RustCrossSigningService(machine)
|
||||
Timber.v(
|
||||
"## CRYPTO | Successfully started up an Olm machine for " +
|
||||
"${userId}, ${deviceId}, identity keys: ${this.olmMachine?.identityKeys()}")
|
||||
|
@ -453,7 +515,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
return verificationService!!
|
||||
}
|
||||
|
||||
override fun crossSigningService() = crossSigningService
|
||||
override fun crossSigningService(): CrossSigningService {
|
||||
if (crossSigningService == null) {
|
||||
internalStart()
|
||||
}
|
||||
|
||||
return crossSigningService!!
|
||||
}
|
||||
|
||||
/**
|
||||
* A sync response has been received
|
||||
|
@ -461,6 +529,15 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
suspend fun onSyncCompleted() {
|
||||
if (isStarted()) {
|
||||
sendOutgoingRequests()
|
||||
// This isn't a copy paste error. Sending the outgoing requests may
|
||||
// claim one-time keys and establish 1-to-1 Olm sessions with devices, while some
|
||||
// outgoing requests are waiting for an Olm session to be established (e.g. forwarding
|
||||
// room keys or sharing secrets).
|
||||
|
||||
// The second call sends out those requests that are waiting for the
|
||||
// keys claim request to be sent out.
|
||||
// This could be omitted but then devices might be waiting for the next
|
||||
sendOutgoingRequests()
|
||||
}
|
||||
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
|
@ -491,7 +568,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
|
||||
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||
return runBlocking {
|
||||
this@DefaultCryptoService.olmMachine?.getUserDevices(userId) ?: listOf()
|
||||
this@DefaultCryptoService.olmMachine?.getCryptoDeviceInfo(userId) ?: listOf()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -572,7 +649,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
* @return the stored device keys for a user.
|
||||
*/
|
||||
override fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
|
||||
return cryptoStore.getUserDevices(userId)?.values?.toMutableList() ?: ArrayList()
|
||||
return this.getCryptoDeviceInfo(userId).toMutableList()
|
||||
}
|
||||
|
||||
private fun isEncryptionEnabledForInvitedUser(): Boolean {
|
||||
|
@ -845,6 +922,11 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
olmMachine!!.markRequestAsSent(request.requestId, RequestType.KEYS_CLAIM, response)
|
||||
}
|
||||
|
||||
private suspend fun signatureUpload(request: Request.SignatureUpload) {
|
||||
this.sender.sendSignatureUpload(request)
|
||||
olmMachine!!.markRequestAsSent(request.requestId, RequestType.SIGNATURE_UPLOAD, "{}")
|
||||
}
|
||||
|
||||
private suspend fun sendOutgoingRequests() {
|
||||
outgoingRequestsLock.withLock {
|
||||
coroutineScope {
|
||||
|
@ -875,6 +957,11 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
sender.sendRoomMessage(it)
|
||||
}
|
||||
}
|
||||
is Request.SignatureUpload -> {
|
||||
async {
|
||||
signatureUpload(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.joinAll()
|
||||
}
|
||||
|
|
|
@ -20,10 +20,14 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.verification.prepareMethods
|
||||
import uniffi.olm.CryptoStoreErrorException
|
||||
import uniffi.olm.Device as InnerDevice
|
||||
import uniffi.olm.OlmMachine
|
||||
import uniffi.olm.SignatureErrorException
|
||||
import uniffi.olm.VerificationRequest
|
||||
|
||||
/** Class representing a device that supports E2EE in the Matrix world
|
||||
|
@ -37,10 +41,27 @@ internal class Device(
|
|||
private val sender: RequestSender,
|
||||
private val listeners: ArrayList<VerificationService.Listener>,
|
||||
) {
|
||||
/** Request an interactive verification to begin
|
||||
@Throws(CryptoStoreErrorException::class)
|
||||
private suspend fun refreshData() {
|
||||
val device = withContext(Dispatchers.IO) {
|
||||
machine.getDevice(inner.userId, inner.deviceId)
|
||||
}
|
||||
|
||||
if (device != null) {
|
||||
this.inner = device
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request an interactive verification to begin
|
||||
*
|
||||
* This sends out a m.key.verification.request event over to-device messaging to
|
||||
* to this device.
|
||||
*
|
||||
* If no specific device should be verified, but we would like to request
|
||||
* verification from all our devices, the
|
||||
* [org.matrix.android.sdk.internal.crypto.OwnUserIdentity.requestVerification]
|
||||
* method can be used instead.
|
||||
*/
|
||||
@Throws(CryptoStoreErrorException::class)
|
||||
suspend fun requestVerification(methods: List<VerificationMethod>): VerificationRequest? {
|
||||
|
@ -61,6 +82,10 @@ internal class Device(
|
|||
*
|
||||
* This sends out a m.key.verification.start event with the method set to
|
||||
* m.sas.v1 to this device using to-device messaging.
|
||||
*
|
||||
* This method will soon be deprecated by [MSC3122](https://github.com/matrix-org/matrix-doc/pull/3122).
|
||||
* The [requestVerification] method should be used instead.
|
||||
*
|
||||
*/
|
||||
@Throws(CryptoStoreErrorException::class)
|
||||
suspend fun startVerification(): SasVerification? {
|
||||
|
@ -78,7 +103,8 @@ internal class Device(
|
|||
}
|
||||
}
|
||||
|
||||
/** Mark this device as locally trusted
|
||||
/**
|
||||
* Mark this device as locally trusted
|
||||
*
|
||||
* This won't upload any signatures, it will only mark the device as trusted
|
||||
* in the local database.
|
||||
|
@ -89,4 +115,56 @@ internal class Device(
|
|||
machine.markDeviceAsTrusted(inner.userId, inner.deviceId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually verify this device
|
||||
*
|
||||
* This will sign the device with our self-signing key and upload the signatures
|
||||
* to the server.
|
||||
*
|
||||
* This will fail if the device doesn't belong to use or if we don't have the
|
||||
* private part of our self-signing key.
|
||||
*/
|
||||
@Throws(SignatureErrorException::class)
|
||||
suspend fun verify(): Boolean {
|
||||
val request = withContext(Dispatchers.IO) {
|
||||
machine.verifyDevice(inner.userId, inner.deviceId)
|
||||
}
|
||||
|
||||
this.sender.sendSignatureUpload(request)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DeviceTrustLevel of this device
|
||||
*/
|
||||
@Throws(CryptoStoreErrorException::class)
|
||||
suspend fun trustLevel(): DeviceTrustLevel {
|
||||
refreshData()
|
||||
return DeviceTrustLevel(crossSigningVerified = inner.crossSigningTrusted, locallyVerified = inner.locallyTrusted)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this device to a CryptoDeviceInfo.
|
||||
*
|
||||
* This will not fetch out fresh data from the Rust side.
|
||||
**/
|
||||
internal fun toCryptoDeviceInfo(): CryptoDeviceInfo {
|
||||
val keys = this.inner.keys.map { (keyId, key) -> "$keyId:$this.inner.deviceId" to key }.toMap()
|
||||
|
||||
return CryptoDeviceInfo(
|
||||
this.inner.deviceId,
|
||||
this.inner.userId,
|
||||
this.inner.algorithms,
|
||||
keys,
|
||||
// The Kotlin side doesn't need to care about signatures,
|
||||
// so we're not filling this out
|
||||
mapOf(),
|
||||
UnsignedDeviceInfo(this.inner.displayName),
|
||||
DeviceTrustLevel(crossSigningVerified = this.inner.crossSigningTrusted, locallyVerified = this.inner.locallyTrusted),
|
||||
this.inner.isBlocked,
|
||||
// TODO
|
||||
null)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,26 +24,34 @@ import java.util.concurrent.ConcurrentHashMap
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.internal.crypto.crosssigning.UserTrustResult
|
||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.session.sync.model.DeviceListResponse
|
||||
import org.matrix.android.sdk.internal.session.sync.model.DeviceOneTimeKeysCountSyncResponse
|
||||
import org.matrix.android.sdk.internal.session.sync.model.ToDeviceSyncResponse
|
||||
import timber.log.Timber
|
||||
import uniffi.olm.CrossSigningKeyExport
|
||||
import uniffi.olm.CrossSigningStatus
|
||||
import uniffi.olm.CryptoStoreErrorException
|
||||
import uniffi.olm.DecryptionErrorException
|
||||
import uniffi.olm.Device as InnerDevice
|
||||
import uniffi.olm.DeviceLists
|
||||
import uniffi.olm.KeyRequestPair
|
||||
import uniffi.olm.Logger
|
||||
|
@ -51,6 +59,7 @@ import uniffi.olm.OlmMachine as InnerMachine
|
|||
import uniffi.olm.ProgressListener as RustProgressListener
|
||||
import uniffi.olm.Request
|
||||
import uniffi.olm.RequestType
|
||||
import uniffi.olm.UserIdentity as RustUserIdentity
|
||||
import uniffi.olm.setLogger
|
||||
|
||||
class CryptoLogger : Logger {
|
||||
|
@ -83,27 +92,34 @@ internal class LiveDevice(
|
|||
}
|
||||
}
|
||||
|
||||
fun setRustLogger() {
|
||||
setLogger(CryptoLogger() as Logger)
|
||||
internal class LiveUserIdentity(
|
||||
internal var userId: String,
|
||||
private var observer: UserIdentityUpdateObserver,
|
||||
) : MutableLiveData<Optional<MXCrossSigningInfo>>() {
|
||||
override fun onActive() {
|
||||
observer.addUserIdentityUpdateListener(this)
|
||||
}
|
||||
|
||||
override fun onInactive() {
|
||||
observer.removeUserIdentityUpdateListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
/** Convert a Rust Device into a Kotlin CryptoDeviceInfo */
|
||||
private fun toCryptoDeviceInfo(device: InnerDevice): CryptoDeviceInfo {
|
||||
val keys = device.keys.map { (keyId, key) -> "$keyId:$device.deviceId" to key }.toMap()
|
||||
internal class LivePrivateCrossSigningKeys(
|
||||
private var observer: PrivateCrossSigningKeysUpdateObserver,
|
||||
) : MutableLiveData<Optional<PrivateKeysInfo>>() {
|
||||
|
||||
return CryptoDeviceInfo(
|
||||
device.deviceId,
|
||||
device.userId,
|
||||
device.algorithms,
|
||||
keys,
|
||||
// TODO pass the signatures here, do we need this, why should the
|
||||
// Kotlin side care about signatures?
|
||||
mapOf(),
|
||||
UnsignedDeviceInfo(device.displayName),
|
||||
DeviceTrustLevel(crossSigningVerified = device.crossSigningTrusted, locallyVerified = device.locallyTrusted),
|
||||
device.isBlocked,
|
||||
// TODO
|
||||
null)
|
||||
override fun onActive() {
|
||||
observer.addUserIdentityUpdateListener(this)
|
||||
}
|
||||
|
||||
override fun onInactive() {
|
||||
observer.removeUserIdentityUpdateListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun setRustLogger() {
|
||||
setLogger(CryptoLogger() as Logger)
|
||||
}
|
||||
|
||||
internal class DeviceUpdateObserver {
|
||||
|
@ -118,6 +134,30 @@ internal class DeviceUpdateObserver {
|
|||
}
|
||||
}
|
||||
|
||||
internal class UserIdentityUpdateObserver {
|
||||
internal val listeners = ConcurrentHashMap<LiveUserIdentity, String>()
|
||||
|
||||
fun addUserIdentityUpdateListener(userIdentity: LiveUserIdentity) {
|
||||
listeners[userIdentity] = userIdentity.userId
|
||||
}
|
||||
|
||||
fun removeUserIdentityUpdateListener(userIdentity: LiveUserIdentity) {
|
||||
listeners.remove(userIdentity)
|
||||
}
|
||||
}
|
||||
|
||||
internal class PrivateCrossSigningKeysUpdateObserver {
|
||||
internal val listeners = ConcurrentHashMap<LivePrivateCrossSigningKeys, Unit>()
|
||||
|
||||
fun addUserIdentityUpdateListener(liveKeys: LivePrivateCrossSigningKeys) {
|
||||
listeners[liveKeys] = Unit
|
||||
}
|
||||
|
||||
fun removeUserIdentityUpdateListener(liveKeys: LivePrivateCrossSigningKeys) {
|
||||
listeners.remove(liveKeys)
|
||||
}
|
||||
}
|
||||
|
||||
internal class OlmMachine(
|
||||
user_id: String,
|
||||
device_id: String,
|
||||
|
@ -127,6 +167,8 @@ internal class OlmMachine(
|
|||
) {
|
||||
private val inner: InnerMachine = InnerMachine(user_id, device_id, path.toString())
|
||||
private val deviceUpdateObserver = deviceObserver
|
||||
private val userIdentityUpdateObserver = UserIdentityUpdateObserver()
|
||||
private val privateKeysUpdateObserver = PrivateCrossSigningKeysUpdateObserver()
|
||||
internal val verificationListeners = ArrayList<VerificationService.Listener>()
|
||||
|
||||
/** Get our own user ID. */
|
||||
|
@ -148,11 +190,42 @@ internal class OlmMachine(
|
|||
return this.inner
|
||||
}
|
||||
|
||||
fun ownDevice(): CryptoDeviceInfo {
|
||||
/** Update all of our live device listeners. */
|
||||
private suspend fun updateLiveDevices() {
|
||||
for ((liveDevice, users) in deviceUpdateObserver.listeners) {
|
||||
val devices = getCryptoDeviceInfo(users)
|
||||
liveDevice.postValue(devices)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateLiveUserIdentities() {
|
||||
for ((liveIdentity, userId) in userIdentityUpdateObserver.listeners) {
|
||||
val identity = getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
|
||||
liveIdentity.postValue(identity)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateLivePrivateKeys() {
|
||||
val keys = this.exportCrossSigningKeys().toOptional()
|
||||
|
||||
for (liveKeys in privateKeysUpdateObserver.listeners.keys()) {
|
||||
liveKeys.postValue(keys)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get our own device info as [CryptoDeviceInfo].
|
||||
*/
|
||||
suspend fun ownDevice(): CryptoDeviceInfo {
|
||||
val deviceId = this.deviceId()
|
||||
|
||||
val keys = this.identityKeys().map { (keyId, key) -> "$keyId:$deviceId" to key }.toMap()
|
||||
|
||||
val crossSigningVerified = when (val ownIdentity = this.getIdentity(this.userId())) {
|
||||
is OwnUserIdentity -> ownIdentity.trustsOurOwnDevice()
|
||||
else -> false
|
||||
}
|
||||
|
||||
return CryptoDeviceInfo(
|
||||
this.deviceId(),
|
||||
this.userId(),
|
||||
|
@ -161,7 +234,7 @@ internal class OlmMachine(
|
|||
keys,
|
||||
mapOf(),
|
||||
UnsignedDeviceInfo(),
|
||||
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
||||
DeviceTrustLevel(crossSigningVerified, locallyVerified = true),
|
||||
false,
|
||||
null)
|
||||
}
|
||||
|
@ -170,7 +243,7 @@ internal class OlmMachine(
|
|||
* Get the list of outgoing requests that need to be sent to the homeserver.
|
||||
*
|
||||
* After the request was sent out and a successful response was received the response body
|
||||
* should be passed back to the state machine using the markRequestAsSent() method.
|
||||
* should be passed back to the state machine using the [markRequestAsSent] method.
|
||||
*
|
||||
* @return the list of requests that needs to be sent to the homeserver
|
||||
*/
|
||||
|
@ -197,6 +270,7 @@ internal class OlmMachine(
|
|||
|
||||
if (requestType == RequestType.KEYS_QUERY) {
|
||||
updateLiveDevices()
|
||||
updateLiveUserIdentities()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -218,8 +292,8 @@ internal class OlmMachine(
|
|||
toDevice: ToDeviceSyncResponse?,
|
||||
deviceChanges: DeviceListResponse?,
|
||||
keyCounts: DeviceOneTimeKeysCountSyncResponse?
|
||||
): ToDeviceSyncResponse =
|
||||
withContext(Dispatchers.IO) {
|
||||
): ToDeviceSyncResponse {
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
val counts: MutableMap<String, Int> = mutableMapOf()
|
||||
|
||||
if (keyCounts?.signedCurve25519 != null) {
|
||||
|
@ -236,6 +310,12 @@ internal class OlmMachine(
|
|||
adapter.fromJson(inner.receiveSyncChanges(events, devices, counts))!!
|
||||
}
|
||||
|
||||
// We may get cross signing keys over a to-device event, update our listeners.
|
||||
this.updateLivePrivateKeys()
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the given list of users to be tracked, triggering a key query request for them.
|
||||
*
|
||||
|
@ -251,13 +331,13 @@ internal class OlmMachine(
|
|||
* Generate one-time key claiming requests for all the users we are missing sessions for.
|
||||
*
|
||||
* After the request was sent out and a successful response was received the response body
|
||||
* should be passed back to the state machine using the markRequestAsSent() method.
|
||||
* should be passed back to the state machine using the [markRequestAsSent] method.
|
||||
*
|
||||
* This method should be called every time before a call to shareRoomKey() is made.
|
||||
* This method should be called every time before a call to [shareRoomKey] is made.
|
||||
*
|
||||
* @param users The list of users for which we would like to establish 1:1 Olm sessions for.
|
||||
*
|
||||
* @return A keys claim request that needs to be sent out to the server.
|
||||
* @return A [Request.KeysClaim] request that needs to be sent out to the server.
|
||||
*/
|
||||
@Throws(CryptoStoreErrorException::class)
|
||||
suspend fun getMissingSessions(users: List<String>): Request? =
|
||||
|
@ -279,7 +359,7 @@ internal class OlmMachine(
|
|||
* @param users The list of users which are considered to be members of the room and should
|
||||
* receive the room key.
|
||||
*
|
||||
* @return The list of requests that need to be sent out.
|
||||
* @return The list of [Request.ToDevice] that need to be sent out.
|
||||
*/
|
||||
@Throws(CryptoStoreErrorException::class)
|
||||
suspend fun shareRoomKey(roomId: String, users: List<String>): List<Request> =
|
||||
|
@ -288,21 +368,18 @@ internal class OlmMachine(
|
|||
/**
|
||||
* Encrypt the given event with the given type and content for the given room.
|
||||
*
|
||||
* **Note**: A room key needs to be shared with the group of users that are members in the given
|
||||
* room. If this is not done this method will panic.
|
||||
* **Note**: A room key needs to be shared with the group of users that are members
|
||||
* in the given room. If this is not done this method will panic.
|
||||
*
|
||||
* The usual flow to encrypt an event using this state machine is as follows:
|
||||
*
|
||||
* 1. Get the one-time key claim request to establish 1:1 Olm sessions for
|
||||
* ```
|
||||
* the room members of the room we wish to participate in. This is done
|
||||
* using the [`get_missing_sessions()`](#method.get_missing_sessions)
|
||||
* method. This method call should be locked per call.
|
||||
* ```
|
||||
* 2. Share a room key with all the room members using the shareRoomKey().
|
||||
* ```
|
||||
* using the [getMissingSessions] method. This method call should be locked per call.
|
||||
*
|
||||
* 2. Share a room key with all the room members using the [shareRoomKey].
|
||||
* This method call should be locked per room.
|
||||
* ```
|
||||
*
|
||||
* 3. Encrypt the event using this method.
|
||||
*
|
||||
* 4. Send the encrypted event to the server.
|
||||
|
@ -316,7 +393,7 @@ internal class OlmMachine(
|
|||
*
|
||||
* @param content the JSON content of the event
|
||||
*
|
||||
* @return The encrypted version of the content
|
||||
* @return The encrypted version of the [Content]
|
||||
*/
|
||||
@Throws(CryptoStoreErrorException::class)
|
||||
suspend fun encrypt(roomId: String, eventType: String, content: Content): Content =
|
||||
|
@ -334,7 +411,7 @@ internal class OlmMachine(
|
|||
*
|
||||
* @param event The serialized encrypted version of the event.
|
||||
*
|
||||
* @return the decrypted version of the event.
|
||||
* @return the decrypted version of the event as a [MXEventDecryptionResult].
|
||||
*/
|
||||
@Throws(MXCryptoError::class)
|
||||
suspend fun decryptRoomEvent(event: Event): MXEventDecryptionResult =
|
||||
|
@ -421,9 +498,44 @@ internal class OlmMachine(
|
|||
ImportRoomKeysResult(result.total, result.imported)
|
||||
}
|
||||
|
||||
@Throws(CryptoStoreErrorException::class)
|
||||
suspend fun getIdentity(userId: String): UserIdentities? {
|
||||
val identity = withContext(Dispatchers.IO) {
|
||||
inner.getIdentity(userId)
|
||||
}
|
||||
val adapter = MoshiProvider.providesMoshi().adapter(RestKeyInfo::class.java)
|
||||
|
||||
return when (identity) {
|
||||
is RustUserIdentity.Other -> {
|
||||
val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel()
|
||||
val selfSigningKey = adapter.fromJson(identity.selfSigningKey)!!.toCryptoModel()
|
||||
|
||||
UserIdentity(identity.userId, masterKey, selfSigningKey, this, this.requestSender)
|
||||
}
|
||||
is RustUserIdentity.Own -> {
|
||||
val masterKey = adapter.fromJson(identity.masterKey)!!.toCryptoModel()
|
||||
val selfSigningKey = adapter.fromJson(identity.selfSigningKey)!!.toCryptoModel()
|
||||
val userSigningKey = adapter.fromJson(identity.userSigningKey)!!.toCryptoModel()
|
||||
|
||||
OwnUserIdentity(
|
||||
identity.userId,
|
||||
masterKey,
|
||||
selfSigningKey,
|
||||
userSigningKey,
|
||||
identity.trustsOurOwnDevice,
|
||||
this,
|
||||
this.requestSender
|
||||
)
|
||||
}
|
||||
null -> null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a `Device` from the store.
|
||||
*
|
||||
* This method returns our own device as well.
|
||||
*
|
||||
* @param userId The id of the device owner.
|
||||
*
|
||||
* @param deviceId The id of the device itself.
|
||||
|
@ -437,21 +549,25 @@ internal class OlmMachine(
|
|||
// using our ownDevice method
|
||||
ownDevice()
|
||||
} else {
|
||||
val device = getRawDevice(userId, deviceId) ?: return null
|
||||
toCryptoDeviceInfo(device)
|
||||
getDevice(userId, deviceId)?.toCryptoDeviceInfo()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getRawDevice(userId: String, deviceId: String): InnerDevice? =
|
||||
withContext(Dispatchers.IO) {
|
||||
inner.getDevice(userId, deviceId)
|
||||
}
|
||||
|
||||
@Throws(CryptoStoreErrorException::class)
|
||||
suspend fun getDevice(userId: String, deviceId: String): Device? {
|
||||
val device = this.getRawDevice(userId, deviceId) ?: return null
|
||||
val device = withContext(Dispatchers.IO) {
|
||||
inner.getDevice(userId, deviceId)
|
||||
} ?: return null
|
||||
|
||||
return Device(this.inner, device, this.requestSender, this.verificationListeners)
|
||||
}
|
||||
|
||||
suspend fun getUserDevices(userId: String): List<Device> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
inner.getUserDevices(userId).map { Device(inner, it, requestSender, verificationListeners) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all devices of an user.
|
||||
*
|
||||
|
@ -460,36 +576,18 @@ internal class OlmMachine(
|
|||
* @return The list of Devices or an empty list if there aren't any.
|
||||
*/
|
||||
@Throws(CryptoStoreErrorException::class)
|
||||
suspend fun getUserDevices(userId: String): List<CryptoDeviceInfo> {
|
||||
val devices = withContext(Dispatchers.IO) {
|
||||
inner.getUserDevices(userId).map { toCryptoDeviceInfo(it) }.toMutableList()
|
||||
}
|
||||
suspend fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||
val devices = this.getUserDevices(userId).map { it.toCryptoDeviceInfo() }.toMutableList()
|
||||
|
||||
// EA doesn't differentiate much between our own and other devices of
|
||||
// while the rust-sdk does, append our own device here.
|
||||
val ownDevice = this.ownDevice()
|
||||
|
||||
if (userId == ownDevice.userId) {
|
||||
devices.add(ownDevice)
|
||||
if (userId == this.userId()) {
|
||||
devices.add(this.ownDevice())
|
||||
}
|
||||
|
||||
return devices
|
||||
}
|
||||
|
||||
suspend fun getUserDevicesMap(userIds: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
|
||||
val userMap = MXUsersDevicesMap<CryptoDeviceInfo>()
|
||||
|
||||
for (user in userIds) {
|
||||
val devices = getUserDevices(user)
|
||||
|
||||
for (device in devices) {
|
||||
userMap.setObject(user, device.deviceId, device)
|
||||
}
|
||||
}
|
||||
|
||||
return userMap
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the devices of multiple users.
|
||||
*
|
||||
|
@ -497,34 +595,45 @@ internal class OlmMachine(
|
|||
*
|
||||
* @return The list of Devices or an empty list if there aren't any.
|
||||
*/
|
||||
suspend fun getUserDevices(userIds: List<String>): List<CryptoDeviceInfo> {
|
||||
private suspend fun getCryptoDeviceInfo(userIds: List<String>): List<CryptoDeviceInfo> {
|
||||
val plainDevices: ArrayList<CryptoDeviceInfo> = arrayListOf()
|
||||
|
||||
for (user in userIds) {
|
||||
val devices = getUserDevices(user)
|
||||
val devices = this.getCryptoDeviceInfo(user)
|
||||
plainDevices.addAll(devices)
|
||||
}
|
||||
|
||||
return plainDevices
|
||||
}
|
||||
|
||||
/** Mark the device for the given user with the given device ID as trusted
|
||||
*
|
||||
* This will mark the device locally as trusted, it won't upload any type of cross
|
||||
* signing signature
|
||||
* */
|
||||
@Throws(CryptoStoreErrorException::class)
|
||||
internal suspend fun markDeviceAsTrusted(userId: String, deviceId: String) =
|
||||
withContext(Dispatchers.IO) {
|
||||
inner.markDeviceAsTrusted(userId, deviceId)
|
||||
suspend fun getUserDevicesMap(userIds: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
|
||||
val userMap = MXUsersDevicesMap<CryptoDeviceInfo>()
|
||||
|
||||
for (user in userIds) {
|
||||
val devices = this.getCryptoDeviceInfo(user)
|
||||
|
||||
for (device in devices) {
|
||||
userMap.setObject(user, device.deviceId, device)
|
||||
}
|
||||
}
|
||||
|
||||
/** Update all of our live device listeners. */
|
||||
private suspend fun updateLiveDevices() {
|
||||
for ((liveDevice, users) in deviceUpdateObserver.listeners) {
|
||||
val devices = getUserDevices(users)
|
||||
liveDevice.postValue(devices)
|
||||
return userMap
|
||||
}
|
||||
|
||||
suspend fun getLiveUserIdentity(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
|
||||
val identity = this.getIdentity(userId)?.toMxCrossSigningInfo().toOptional()
|
||||
val liveIdentity = LiveUserIdentity(userId, this.userIdentityUpdateObserver)
|
||||
liveIdentity.value = identity
|
||||
|
||||
return liveIdentity
|
||||
}
|
||||
|
||||
suspend fun getLivePrivateCrossSigningKeys(): LiveData<Optional<PrivateKeysInfo>> {
|
||||
val keys = this.exportCrossSigningKeys().toOptional()
|
||||
val liveKeys = LivePrivateCrossSigningKeys(this.privateKeysUpdateObserver)
|
||||
liveKeys.value = keys
|
||||
|
||||
return liveKeys
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -538,7 +647,7 @@ internal class OlmMachine(
|
|||
* @return The list of Devices or an empty list if there aren't any.
|
||||
*/
|
||||
suspend fun getLiveDevices(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
|
||||
val plainDevices = getUserDevices(userIds)
|
||||
val plainDevices = getCryptoDeviceInfo(userIds)
|
||||
val devices = LiveDevice(userIds, deviceUpdateObserver)
|
||||
devices.value = plainDevices
|
||||
|
||||
|
@ -556,7 +665,7 @@ internal class OlmMachine(
|
|||
* @param userId The ID of the user for which we would like to fetch the
|
||||
* verification requests
|
||||
*
|
||||
* @return The list of VerificationRequests that we share with the given user
|
||||
* @return The list of [VerificationRequest] that we share with the given user
|
||||
*/
|
||||
fun getVerificationRequests(userId: String): List<VerificationRequest> {
|
||||
return this.inner.getVerificationRequests(userId).map {
|
||||
|
@ -585,9 +694,10 @@ internal class OlmMachine(
|
|||
}
|
||||
}
|
||||
|
||||
/** Get an active verification for the given user and given flow ID
|
||||
/** Get an active verification for the given user and given flow ID.
|
||||
*
|
||||
* This can return a SAS verification or a QR code verification
|
||||
* @return Either a [SasVerification] verification or a [QrCodeVerification]
|
||||
* verification.
|
||||
*/
|
||||
fun getVerification(userId: String, flowId: String): VerificationTransaction? {
|
||||
return when (val verification = this.inner.getVerification(userId, flowId)) {
|
||||
|
@ -613,4 +723,41 @@ internal class OlmMachine(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun bootstrapCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
|
||||
val requests = withContext(Dispatchers.IO) {
|
||||
inner.bootstrapCrossSigning()
|
||||
}
|
||||
|
||||
this.requestSender.uploadCrossSigningKeys(requests.uploadSigningKeysRequest, uiaInterceptor)
|
||||
this.requestSender.sendSignatureUpload(requests.signatureRequest)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status of our private cross signing keys, i.e. which private keys do we have stored locally.
|
||||
*/
|
||||
fun crossSigningStatus(): CrossSigningStatus {
|
||||
return this.inner.crossSigningStatus()
|
||||
}
|
||||
|
||||
suspend fun exportCrossSigningKeys(): PrivateKeysInfo? {
|
||||
val export = withContext(Dispatchers.IO) {
|
||||
inner.exportCrossSigningKeys()
|
||||
|
||||
} ?: return null
|
||||
|
||||
return PrivateKeysInfo(export.masterKey, export.selfSigningKey, export.userSigningKey)
|
||||
}
|
||||
|
||||
suspend fun importCrossSigningKeys(export: PrivateKeysInfo): UserTrustResult {
|
||||
val rustExport = CrossSigningKeyExport(export.master, export.selfSigned, export.user)
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
inner.importCrossSigningKeys(rustExport)
|
||||
}
|
||||
|
||||
this.updateLivePrivateKeys()
|
||||
// TODO map the errors from importCrossSigningKeys to the UserTrustResult
|
||||
return UserTrustResult.Success
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
|||
import org.matrix.android.sdk.internal.crypto.verification.UpdateDispatcher
|
||||
import uniffi.olm.CryptoStoreErrorException
|
||||
import uniffi.olm.OlmMachine
|
||||
import uniffi.olm.OutgoingVerificationRequest
|
||||
import uniffi.olm.QrCode
|
||||
import uniffi.olm.Verification
|
||||
|
||||
|
|
|
@ -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.MessageType
|
||||
import org.matrix.android.sdk.internal.crypto.OlmMachine
|
||||
import org.matrix.android.sdk.internal.crypto.RequestSender
|
||||
import org.matrix.android.sdk.internal.crypto.OwnUserIdentity
|
||||
import org.matrix.android.sdk.internal.crypto.SasVerification
|
||||
import org.matrix.android.sdk.internal.crypto.VerificationRequest
|
||||
import org.matrix.android.sdk.internal.crypto.UserIdentity
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
||||
|
@ -124,10 +124,7 @@ internal class UpdateDispatcher(private val listeners: ArrayList<VerificationSer
|
|||
}
|
||||
}
|
||||
|
||||
internal class RustVerificationService(
|
||||
private val olmMachine: OlmMachine,
|
||||
private val requestSender: RequestSender,
|
||||
) : VerificationService {
|
||||
internal class RustVerificationService(private val olmMachine: OlmMachine) : VerificationService {
|
||||
private val dispatcher = UpdateDispatcher(this.olmMachine.verificationListeners)
|
||||
|
||||
/** The main entry point for the verification service
|
||||
|
@ -280,19 +277,13 @@ internal class RustVerificationService(
|
|||
otherUserId: String,
|
||||
otherDevices: List<String>?
|
||||
): PendingVerificationRequest {
|
||||
val stringMethods = prepareMethods(methods)
|
||||
|
||||
val result = this.olmMachine.inner().requestSelfVerification(stringMethods)
|
||||
runBlocking {
|
||||
requestSender.sendVerificationRequest(result!!.request)
|
||||
val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) {
|
||||
is OwnUserIdentity -> runBlocking { identity.requestVerification(methods) }
|
||||
is UserIdentity -> throw IllegalArgumentException("This method doesn't support verification of other users devices")
|
||||
null -> throw IllegalArgumentException("Cross signing has not been bootstrapped for our own user")
|
||||
}
|
||||
|
||||
return VerificationRequest(
|
||||
this.olmMachine.inner(),
|
||||
result!!.verification,
|
||||
this.requestSender,
|
||||
this.olmMachine.verificationListeners
|
||||
).toPendingVerificationRequest()
|
||||
return verification.toPendingVerificationRequest()
|
||||
}
|
||||
|
||||
override fun requestKeyVerificationInDMs(
|
||||
|
@ -302,20 +293,13 @@ internal class RustVerificationService(
|
|||
localId: String?
|
||||
): PendingVerificationRequest {
|
||||
Timber.i("## SAS Requesting verification to user: $otherUserId in room $roomId")
|
||||
val stringMethods = prepareMethods(methods)
|
||||
val content = this.olmMachine.inner().verificationRequestContent(otherUserId, stringMethods)!!
|
||||
|
||||
val eventID = runBlocking {
|
||||
requestSender.sendRoomMessage(EventType.MESSAGE, roomId, content, localId!!)
|
||||
val verification = when (val identity = runBlocking { olmMachine.getIdentity(otherUserId) }) {
|
||||
is UserIdentity -> runBlocking { identity.requestVerification(methods, roomId, localId!!) }
|
||||
is OwnUserIdentity -> throw IllegalArgumentException("This method doesn't support verification of our own user")
|
||||
null -> throw IllegalArgumentException("The user that we wish to verify doesn't support cross signing")
|
||||
}
|
||||
|
||||
val innerRequest = this.olmMachine.inner().requestVerification(otherUserId, roomId, eventID, stringMethods)!!
|
||||
return VerificationRequest(
|
||||
this.olmMachine.inner(),
|
||||
innerRequest,
|
||||
this.requestSender,
|
||||
this.olmMachine.verificationListeners
|
||||
).toPendingVerificationRequest()
|
||||
return verification.toPendingVerificationRequest()
|
||||
}
|
||||
|
||||
override fun readyPendingVerification(
|
||||
|
|
Loading…
Reference in New Issue