mirror of
https://github.com/element-hq/element-android
synced 2024-12-19 15:58:27 +03:00
crypto: More docs
This commit is contained in:
parent
6d05f5b993
commit
08d0787cc9
2 changed files with 266 additions and 76 deletions
|
@ -143,14 +143,23 @@ internal class OlmMachine(user_id: String, device_id: String, path: File) {
|
||||||
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 = DeviceUpdateObserver()
|
private val deviceUpdateObserver = DeviceUpdateObserver()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get our own user ID.
|
||||||
|
*/
|
||||||
fun userId(): String {
|
fun userId(): String {
|
||||||
return this.inner.userId()
|
return this.inner.userId()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get our own device ID.
|
||||||
|
*/
|
||||||
fun deviceId(): String {
|
fun deviceId(): String {
|
||||||
return this.inner.deviceId()
|
return this.inner.deviceId()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get our own public identity keys ID.
|
||||||
|
*/
|
||||||
fun identityKeys(): Map<String, String> {
|
fun identityKeys(): Map<String, String> {
|
||||||
return this.inner.identityKeys()
|
return this.inner.identityKeys()
|
||||||
}
|
}
|
||||||
|
@ -170,36 +179,56 @@ internal class OlmMachine(user_id: String, device_id: String, path: File) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateLiveDevices() {
|
/**
|
||||||
for ((liveDevice, users) in deviceUpdateObserver.listeners) {
|
* Get the list of outgoing requests that need to be sent to the homeserver.
|
||||||
val devices = getUserDevices(users)
|
*
|
||||||
liveDevice.postValue(devices)
|
* 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
|
||||||
}
|
* mark_request_as_sent method.
|
||||||
|
*
|
||||||
|
* @return the list of requests that needs to be sent to the homeserver
|
||||||
|
*/
|
||||||
suspend fun outgoingRequests(): List<Request> = withContext(Dispatchers.IO) {
|
suspend fun outgoingRequests(): List<Request> = withContext(Dispatchers.IO) {
|
||||||
inner.outgoingRequests()
|
inner.outgoingRequests()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun encrypt(roomId: String, eventType: String, content: Content): Content = withContext(Dispatchers.IO) {
|
/**
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter<Content>(Map::class.java)
|
* Mark a request that was sent to the server as sent.
|
||||||
val contentString = adapter.toJson(content)
|
*
|
||||||
val encrypted = inner.encrypt(roomId, eventType, contentString)
|
* @param requestId The unique ID of the request that was sent out. This needs to be an UUID.
|
||||||
adapter.fromJson(encrypted)!!
|
*
|
||||||
|
* @param requestType The type of the request that was sent out.
|
||||||
|
*
|
||||||
|
* @param responseBody The body of the response that was received.
|
||||||
|
*/
|
||||||
|
suspend fun markRequestAsSent(
|
||||||
|
requestId: String,
|
||||||
|
requestType: RequestType,
|
||||||
|
responseBody: String
|
||||||
|
) = withContext(Dispatchers.IO) {
|
||||||
|
inner.markRequestAsSent(requestId, requestType, responseBody)
|
||||||
|
|
||||||
|
if (requestType == RequestType.KEYS_QUERY) {
|
||||||
|
updateLiveDevices()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun shareGroupSession(roomId: String, users: List<String>): List<Request> = withContext(Dispatchers.IO) {
|
|
||||||
inner.shareGroupSession(roomId, users)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getMissingSessions(users: List<String>): Request? = withContext(Dispatchers.IO) {
|
|
||||||
inner.getMissingSessions(users)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun updateTrackedUsers(users: List<String>) = withContext(Dispatchers.IO) {
|
|
||||||
inner.updateTrackedUsers(users)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Let the state machine know about E2EE related sync changes that we
|
||||||
|
* received from the server.
|
||||||
|
*
|
||||||
|
* This needs to be called after every sync, ideally before processing
|
||||||
|
* any other sync changes.
|
||||||
|
*
|
||||||
|
* @param toDevice A serialized array of to-device events we received in the
|
||||||
|
* current sync resposne.
|
||||||
|
*
|
||||||
|
* @param deviceChanges The list of devices that have changed in some way
|
||||||
|
* since the previous sync.
|
||||||
|
*
|
||||||
|
* @param keyCounts The map of uploaded one-time key types and counts.
|
||||||
|
*/
|
||||||
suspend fun receiveSyncChanges(
|
suspend fun receiveSyncChanges(
|
||||||
toDevice: ToDeviceSyncResponse?,
|
toDevice: ToDeviceSyncResponse?,
|
||||||
deviceChanges: DeviceListResponse?,
|
deviceChanges: DeviceListResponse?,
|
||||||
|
@ -218,64 +247,114 @@ internal class OlmMachine(user_id: String, device_id: String, path: File) {
|
||||||
inner.receiveSyncChanges(events, devices, counts)
|
inner.receiveSyncChanges(events, devices, counts)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markRequestAsSent(
|
/**
|
||||||
request_id: String,
|
* Mark the given list of users to be tracked, triggering a key query request
|
||||||
request_type: RequestType,
|
* for them.
|
||||||
response_body: String
|
*
|
||||||
) = withContext(Dispatchers.IO) {
|
* *Note*: Only users that aren't already tracked will be considered for an
|
||||||
inner.markRequestAsSent(request_id, request_type, response_body)
|
* update. It's safe to call this with already tracked users, it won't
|
||||||
|
* result in excessive keys query requests.
|
||||||
if (request_type == RequestType.KEYS_QUERY) {
|
*
|
||||||
updateLiveDevices()
|
* @param users The users that should be queued up for a key query.
|
||||||
}
|
*/
|
||||||
|
suspend fun updateTrackedUsers(users: List<String>) = withContext(Dispatchers.IO) {
|
||||||
|
inner.updateTrackedUsers(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getDevice(user_id: String, device_id: String): Device? = withContext(Dispatchers.IO) {
|
|
||||||
when (val device: InnerDevice? = inner.getDevice(user_id, device_id)) {
|
/**
|
||||||
null -> null
|
* Generate one-time key claiming requests for all the users we are missing
|
||||||
else -> Device(device, inner)
|
* 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
|
||||||
|
* mark_request_as_sent() method.
|
||||||
|
*
|
||||||
|
* This method should be called every time before a call to
|
||||||
|
* share_group_session() 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.
|
||||||
|
*/
|
||||||
|
suspend fun getMissingSessions(users: List<String>): Request? = withContext(Dispatchers.IO) {
|
||||||
|
inner.getMissingSessions(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getUserDevices(userId: String): List<CryptoDeviceInfo> {
|
/**
|
||||||
return inner.getUserDevices(userId).map { Device(it, inner).toCryptoDeviceInfo() }
|
* Share a room key with the given list of users for the given room.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* mark_request_as_sent() method.
|
||||||
|
*
|
||||||
|
* This method should be called every time before a call to
|
||||||
|
* `encrypt()` with the given `room_id` is made.
|
||||||
|
*
|
||||||
|
* @param roomId The unique id of the room, note that this doesn't strictly
|
||||||
|
* need to be a Matrix room, it just needs to be an unique identifier for
|
||||||
|
* the group that will participate in the conversation.
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
suspend fun shareGroupSession(roomId: String, users: List<String>): List<Request> = withContext(Dispatchers.IO) {
|
||||||
|
inner.shareGroupSession(roomId, users)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getUserDevices(userIds: List<String>): List<CryptoDeviceInfo> {
|
/**
|
||||||
val plainDevices: ArrayList<CryptoDeviceInfo> = arrayListOf()
|
* Encrypt the given event with the given type and content for the given
|
||||||
|
* room.
|
||||||
for (user in userIds) {
|
*
|
||||||
val devices = getUserDevices(user)
|
* **Note**: A room key needs to be shared with the group of users that are
|
||||||
plainDevices.addAll(devices)
|
* members in the given room. If this is not done this method will panic.
|
||||||
}
|
*
|
||||||
|
* The usual flow to encrypt an evnet using this state machine is as
|
||||||
return plainDevices
|
* follows:
|
||||||
}
|
*
|
||||||
|
* 1. Get the one-time key claim request to establish 1:1 Olm sessions for
|
||||||
suspend fun getLiveDevices(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
|
* the room members of the room we wish to participate in. This is done
|
||||||
val plainDevices = getUserDevices(userIds)
|
* using the [`get_missing_sessions()`](#method.get_missing_sessions)
|
||||||
val devices = LiveDevice(userIds, deviceUpdateObserver)
|
* method. This method call should be locked per call.
|
||||||
devices.setValue(plainDevices)
|
*
|
||||||
|
* 2. Share a room key with all the room members using the share_group_session().
|
||||||
return devices
|
* This method call should be locked per room.
|
||||||
}
|
*
|
||||||
|
* 3. Encrypt the event using this method.
|
||||||
@Throws(CryptoStoreErrorException::class)
|
*
|
||||||
suspend fun exportKeys(passphrase: String, rounds: Int): ByteArray = withContext(Dispatchers.IO) {
|
* 4. Send the encrypted event to the server.
|
||||||
inner.exportKeys(passphrase, rounds).toByteArray()
|
*
|
||||||
}
|
* After the room key is shared steps 1 and 2 will become noops, unless
|
||||||
|
* there's some changes in the room membership or in the list of devices a
|
||||||
@Throws(CryptoStoreErrorException::class)
|
* member has.
|
||||||
suspend fun importKeys(keys: ByteArray, passphrase: String, listener: ProgressListener?): ImportRoomKeysResult = withContext(Dispatchers.IO) {
|
*
|
||||||
var decodedKeys = keys.toString()
|
* @param roomId the ID of the room where the encrypted event will be sent to
|
||||||
|
*
|
||||||
var rustListener = CryptoProgressListener(listener)
|
* @param eventType the type of the event
|
||||||
|
*
|
||||||
var result = inner.importKeys(decodedKeys, passphrase, rustListener)
|
* @param content the JSON content of the event
|
||||||
|
*
|
||||||
ImportRoomKeysResult(result.total, result.imported)
|
* @return The encrypted version of the content
|
||||||
|
*/
|
||||||
|
suspend fun encrypt(roomId: String, eventType: String, content: Content): Content = withContext(Dispatchers.IO) {
|
||||||
|
val adapter = MoshiProvider.providesMoshi().adapter<Content>(Map::class.java)
|
||||||
|
val contentString = adapter.toJson(content)
|
||||||
|
val encrypted = inner.encrypt(roomId, eventType, contentString)
|
||||||
|
adapter.fromJson(encrypted)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt the given event that was sent in the given room.
|
||||||
|
*
|
||||||
|
* # Arguments
|
||||||
|
*
|
||||||
|
* @param event The serialized encrypted version of the event.
|
||||||
|
*
|
||||||
|
* @return the decrypted version of the event.
|
||||||
|
*/
|
||||||
@Throws(MXCryptoError::class)
|
@Throws(MXCryptoError::class)
|
||||||
suspend fun decryptRoomEvent(event: Event): MXEventDecryptionResult = withContext(Dispatchers.IO) {
|
suspend fun decryptRoomEvent(event: Event): MXEventDecryptionResult = withContext(Dispatchers.IO) {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter<Event>(Event::class.java)
|
val adapter = MoshiProvider.providesMoshi().adapter<Event>(Event::class.java)
|
||||||
|
@ -298,4 +377,116 @@ internal class OlmMachine(user_id: String, device_id: String, path: File) {
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export all of our room keys.
|
||||||
|
*
|
||||||
|
* @param passphrase The passphrase that should be used to encrypt the key
|
||||||
|
* export.
|
||||||
|
*
|
||||||
|
* @param rounds The number of rounds that should be used when expanding the
|
||||||
|
* passphrase into an key.
|
||||||
|
*
|
||||||
|
* @return the encrypted key export as a bytearray.
|
||||||
|
*/
|
||||||
|
@Throws(CryptoStoreErrorException::class)
|
||||||
|
suspend fun exportKeys(passphrase: String, rounds: Int): ByteArray = withContext(Dispatchers.IO) {
|
||||||
|
inner.exportKeys(passphrase, rounds).toByteArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import room keys from the given serialized key export.
|
||||||
|
*
|
||||||
|
* @param keys The serialized version of the key export.
|
||||||
|
*
|
||||||
|
* @param passphrase The passphrase that was used to encrypt the key export.
|
||||||
|
*
|
||||||
|
* @param listener A callback that can be used to introspect the
|
||||||
|
* progress of the key import.
|
||||||
|
*/
|
||||||
|
@Throws(CryptoStoreErrorException::class)
|
||||||
|
suspend fun importKeys(keys: ByteArray, passphrase: String, listener: ProgressListener?): ImportRoomKeysResult = withContext(Dispatchers.IO) {
|
||||||
|
var decodedKeys = keys.toString()
|
||||||
|
|
||||||
|
var rustListener = CryptoProgressListener(listener)
|
||||||
|
|
||||||
|
var result = inner.importKeys(decodedKeys, passphrase, rustListener)
|
||||||
|
|
||||||
|
ImportRoomKeysResult(result.total, result.imported)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a `Device` from the store.
|
||||||
|
*
|
||||||
|
* @param userId The id of the device owner.
|
||||||
|
*
|
||||||
|
* @param deviceId The id of the device itself.
|
||||||
|
*
|
||||||
|
* @return The Device if it found one.
|
||||||
|
*/
|
||||||
|
suspend fun getDevice(userId: String, deviceId: String): Device? = withContext(Dispatchers.IO) {
|
||||||
|
when (val device: InnerDevice? = inner.getDevice(userId, deviceId)) {
|
||||||
|
null -> null
|
||||||
|
else -> Device(device, inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all devices of an user.
|
||||||
|
*
|
||||||
|
* @param userId The id of the device owner.
|
||||||
|
*
|
||||||
|
* @return The list of Devices or an empty list if there aren't any.
|
||||||
|
*/
|
||||||
|
suspend fun getUserDevices(userId: String): List<CryptoDeviceInfo> {
|
||||||
|
return inner.getUserDevices(userId).map { Device(it, inner).toCryptoDeviceInfo() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the devices of multiple users.
|
||||||
|
*
|
||||||
|
* @param userId The ids of the device owners.
|
||||||
|
*
|
||||||
|
* @return The list of Devices or an empty list if there aren't any.
|
||||||
|
*/
|
||||||
|
suspend fun getUserDevices(userIds: List<String>): List<CryptoDeviceInfo> {
|
||||||
|
val plainDevices: ArrayList<CryptoDeviceInfo> = arrayListOf()
|
||||||
|
|
||||||
|
for (user in userIds) {
|
||||||
|
val devices = getUserDevices(user)
|
||||||
|
plainDevices.addAll(devices)
|
||||||
|
}
|
||||||
|
|
||||||
|
return plainDevices
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update all of our live device listeners.
|
||||||
|
*/
|
||||||
|
private suspend fun updateLiveDevices() {
|
||||||
|
for ((liveDevice, users) in deviceUpdateObserver.listeners) {
|
||||||
|
val devices = getUserDevices(users)
|
||||||
|
liveDevice.postValue(devices)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the devices of multiple users as a live version.
|
||||||
|
*
|
||||||
|
* The live version will update the list of devices if some of the data
|
||||||
|
* changes, or if new devices arrive for a certain user.
|
||||||
|
*
|
||||||
|
* @param userId The ids of the device owners.
|
||||||
|
*
|
||||||
|
* @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 devices = LiveDevice(userIds, deviceUpdateObserver)
|
||||||
|
devices.setValue(plainDevices)
|
||||||
|
|
||||||
|
return devices
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl OlmMachine {
|
||||||
///
|
///
|
||||||
/// * `user_id` - The id of the device owner.
|
/// * `user_id` - The id of the device owner.
|
||||||
///
|
///
|
||||||
/// * `device_id` - The id of the device itsefl.
|
/// * `device_id` - The id of the device itself.
|
||||||
pub fn get_device(&self, user_id: &str, device_id: &str) -> Option<Device> {
|
pub fn get_device(&self, user_id: &str, device_id: &str) -> Option<Device> {
|
||||||
let user_id = UserId::try_from(user_id).unwrap();
|
let user_id = UserId::try_from(user_id).unwrap();
|
||||||
|
|
||||||
|
@ -276,8 +276,7 @@ impl OlmMachine {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate one-time key claiming requests for all the users we are missing
|
/// Share a room key with the given list of users for the given room.
|
||||||
/// sessions for.
|
|
||||||
///
|
///
|
||||||
/// After the request was sent out and a successful response was received
|
/// 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
|
/// the response body should be passed back to the state machine using the
|
||||||
|
|
Loading…
Reference in a new issue