Merge tag 'v1.6.22' into sc

tag

Change-Id: I639c817f96e79b891af3052032caf6e6464c23aa
This commit is contained in:
SpiritCroc 2024-09-24 18:53:46 +02:00
commit 3f6c2d60ad
164 changed files with 394 additions and 7611 deletions

View file

@ -1,3 +1,15 @@
Changes in Element v1.6.22 (2024-09-23)
=======================================
Important: this version removes the dependency of the deprecated libolm library.
Application installations that have not been updated to the first version with the migration (1.6.3 release at 2023-06-27) will not be able to migrate the account.
More details in ([#8901](https://github.com/element-hq/element-android/issues/8901))
Other changes
-------------
- Remove legacy QR code login. ([#8889](https://github.com/element-hq/element-android/issues/8889))
Changes in Element v1.6.20 (2024-07-25)
=======================================
@ -13,7 +25,7 @@ Bugfixes 🐛
----------
- Fix redacted events not grouped correctly when hidden events are inserted between. ([#8840](https://github.com/element-hq/element-android/issues/8840))
- Element-Android session doesn't encrypt for a dehydrated device ([#8842](https://github.com/element-hq/element-android/issues/8842))
- Intercept only links from `element.io` well known hosts. The previous behaviour broke OIDC login in Element X. ([#8894](https://github.com/element-hq/element-android/issues/8894))
- Intercept only links from `element.io` well known hosts. The previous behaviour broke OIDC login in Element X. ([#8849](https://github.com/element-hq/element-android/issues/8849))
Other changes
-------------

View file

@ -88,9 +88,9 @@ allprojects {
}
// Jitsi repo
maven {
url "https://github.com/element-hq/jitsi_libre_maven/raw/main/android-sdk-8.1.1"
url "https://github.com/element-hq/jitsi_libre_maven/raw/main/mobile-sdk-10.2.0"
// Note: to test Jitsi release you can use a local file like this:
// url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-8.1.1"
// url "file:///Users/bmarty/workspaces/jitsi_libre_maven/mobile-sdk-10.2.0"
content {
groups.jitsi.regex.each { includeGroupByRegex it }
groups.jitsi.group.each { includeGroup it }

View file

@ -73,6 +73,7 @@ ext.groups = [
'com.facebook.flipper',
'com.facebook.fresco',
'com.facebook.infer.annotation',
'com.facebook.react',
'com.facebook.soloader',
'com.facebook.stetho',
'com.facebook.yoga',

View file

@ -28,18 +28,18 @@ The generated maven repository is then host in the project https://github.com/el
#### Jitsi version
Update the script `./tools/jitsi/build_jisti_libs.sh` with the tag of the project `https://github.com/jitsi/jitsi-meet`.
Update the script `./tools/jitsi/build_jitsi_libs.sh` with the tag of the project `https://github.com/jitsi/jitsi-meet`.
Latest tag can be found from this page: https://github.com/jitsi/jitsi-meet-release-notes/blob/master/CHANGELOG-MOBILE-SDKS.md
Currently we are building the version with the tag `android-sdk-8.1.1`.
Currently we are building the version with the tag `mobile-sdk-10.2.0`.
#### Run the build script
At the root of the Element Android, run the following script:
```shell script
./tools/jitsi/build_jisti_libs.sh
./tools/jitsi/build_jitsi_libs.sh
```
It will build the Jitsi Meet Android library and put every generated files in the folder `/tmp/jitsi`
@ -49,7 +49,7 @@ It will build the Jitsi Meet Android library and put every generated files in th
- Update the file `./build.gradle` to use the previously created local Maven repository. Currently we have this line:
```groovy
url "https://github.com/element-hq/jitsi_libre_maven/raw/main/android-sdk-8.1.1"
url "https://github.com/element-hq/jitsi_libre_maven/raw/main/mobile-sdk-10.2.0"
```
You can uncomment and update the line starting with `// url "file://...` and comment the line starting with `url`, to test the library using the locally generated Maven repository.
@ -57,13 +57,7 @@ You can uncomment and update the line starting with `// url "file://...` and com
- Update the dependency of the Jitsi Meet library in the file `./vector/build.gradle`. Currently we have this line:
```groovy
api('org.jitsi.react:jitsi-meet-sdk:8.1.1')
```
- Update the dependency of the WebRTC library in the file `./vector/build.gradle`. Currently we have this line:
```groovy
implementation('com.facebook.react:react-native-webrtc:111.0.0-jitsi-13672566@aar')
api('org.jitsi.react:jitsi-meet-sdk:10.2.0')
```
- Perform a gradle sync and build the project
@ -88,7 +82,7 @@ If all the tests are passed, you can export the generated Jitsi library to our M
- Update the file `./build.gradle` to use the previously created Maven repository. Currently we have this line:
```groovy
url "https://github.com/element-hq/jitsi_libre_maven/raw/main/android-sdk-8.1.1"
url "https://github.com/element-hq/jitsi_libre_maven/raw/main/mobile-sdk-10.2.0"
```
- Build the project and perform the sanity tests again.

View file

@ -0,0 +1,2 @@
Main changes in this version: crypto sdk upgrade.
Full changelog: https://github.com/element-hq/element-android/releases

View file

@ -1175,9 +1175,9 @@
<!-- room settings : alias -->
<string name="room_settings_alias_title">Room addresses</string>
<string name="room_settings_alias_subtitle">See and managed addresses of this room, and its visibility in the room directory.</string>
<string name="room_settings_alias_subtitle">See and manage addresses of this room, and its visibility in the room directory.</string>
<string name="space_settings_alias_title">Space addresses</string>
<string name="space_settings_alias_subtitle">See and managed addresses of this space.</string>
<string name="space_settings_alias_subtitle">See and manage addresses of this space.</string>
<string name="room_alias_published_alias_title">Published Addresses</string>
<string name="room_alias_published_alias_subtitle">Published addresses can be used by anyone on any server to join your room. To publish an address, it needs to be set as a local address first.</string>
@ -2228,7 +2228,8 @@
<string name="login_signin_matrix_id_password_notice">If you dont know your password, go back to reset it.</string>
<string name="login_signin_matrix_id_error_invalid_matrix_id">This is not a valid user identifier. Expected format: \'@user:homeserver.org\'</string>
<string name="autodiscover_well_known_error">Unable to find a valid homeserver. Please check your identifier</string>
<string name="login_scan_qr_code">Scan QR code</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="login_scan_qr_code">Scan QR code</string>
<string name="seen_by">Seen by</string>
@ -3475,9 +3476,10 @@
<string name="device_manager_session_rename_edit_hint">Session name</string>
<string name="device_manager_session_rename_description">Custom session names can help you recognize your devices more easily.</string>
<string name="device_manager_session_rename_warning">Please be aware that session names are also visible to people you communicate with.</string>
<string name="device_manager_sessions_sign_in_with_qr_code_title">Sign in with QR Code</string>
<string name="device_manager_sessions_sign_in_with_qr_code_description">You can use this device to sign in a mobile or web device with a QR code. There are two ways to do this:</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="device_manager_sessions_sign_in_with_qr_code_title">Sign in with QR Code</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="device_manager_sessions_sign_in_with_qr_code_description">You can use this device to sign in a mobile or web device with a QR code. There are two ways to do this:</string>
<string name="device_manager_learn_more_sessions_inactive_title">Inactive sessions</string>
<string name="device_manager_learn_more_sessions_inactive">Inactive sessions are sessions you have not used in some time, but they continue to receive encryption keys.\n\nRemoving inactive sessions improves security and performance, and makes it easier for you to identify if a new session is suspicious.</string>
<string name="device_manager_learn_more_sessions_unverified_title">Unverified sessions</string>
@ -3515,45 +3517,82 @@
<string name="onboarding_new_app_layout_feedback_message">Tap top right to see the option to feedback.</string>
<string name="onboarding_new_app_layout_button_try">Try it out</string>
<string name="one">1</string>
<string name="two">2</string>
<string name="three">3</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="one">1</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="two">2</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="three">3</string>
<!-- QR Code Login -->
<string name="qr_code_login_header_scan_qr_code_title">Scan QR code</string>
<string name="qr_code_login_header_scan_qr_code_description">Use the camera on this device to scan the QR code shown on your other device:</string>
<string name="qr_code_login_header_show_qr_code_title">Sign in with QR code</string>
<string name="qr_code_login_header_show_qr_code_new_device_description">Use your signed in device to scan the QR code below:</string>
<string name="qr_code_login_header_show_qr_code_link_a_device_description">Scan the QR code below with your device thats signed out.</string>
<string name="qr_code_login_header_connected_title">Secure connection established</string>
<string name="qr_code_login_header_connected_description">Check your signed in device, the code below should be displayed. Confirm that the code below matches with that device:</string>
<string name="qr_code_login_header_failed_title">Unsuccessful connection</string>
<string name="qr_code_login_header_failed_device_is_not_supported_description">Linking with this device is not supported.</string>
<string name="qr_code_login_header_failed_timeout_description">The linking wasnt completed in the required time.</string>
<string name="qr_code_login_header_failed_denied_description">The request was denied on the other device.</string>
<string name="qr_code_login_header_failed_other_description">The request failed.</string>
<string name="qr_code_login_header_failed_e2ee_security_issue_description">A security issue was encountered setting up secure messaging. One of the following may be compromised: Your homeserver; Your internet connection(s); Your device(s);</string>
<string name="qr_code_login_header_failed_other_device_already_signed_in_description">The other device is already signed in.</string>
<string name="qr_code_login_header_failed_other_device_not_signed_in_description">The other device must be signed in.</string>
<string name="qr_code_login_header_failed_invalid_qr_code_description">That QR code is invalid.</string>
<string name="qr_code_login_header_failed_user_cancelled_description">The sign in was cancelled on the other device.</string>
<string name="qr_code_login_header_failed_homeserver_is_not_supported_description">The homeserver doesn\'t support sign in with QR code.</string>
<string name="qr_code_login_new_device_instruction_1">Open the app on your other device</string>
<string name="qr_code_login_new_device_instruction_2">Go to Settings -> Security &amp; Privacy</string>
<string name="qr_code_login_new_device_instruction_3">Select \'Show QR code\'</string>
<string name="qr_code_login_link_a_device_scan_qr_code_instruction_1">Start at the sign in screen</string>
<string name="qr_code_login_link_a_device_scan_qr_code_instruction_2">Select \'Sign in with QR code\'</string>
<string name="qr_code_login_link_a_device_show_qr_code_instruction_1">Start at the sign in screen</string>
<string name="qr_code_login_link_a_device_show_qr_code_instruction_2">Select \'Scan QR code\'</string>
<string name="qr_code_login_show_qr_code_button">Show QR code in this device</string>
<string name="qr_code_login_signing_in_a_mobile_device">Signing in a mobile device?</string>
<string name="qr_code_login_scan_qr_code_button">Scan QR code</string>
<string name="qr_code_login_connecting_to_device">Connecting to device</string>
<string name="qr_code_login_signing_in">Signing you in</string>
<string name="qr_code_login_status_no_match">No match?</string>
<string name="qr_code_login_try_again">Try again</string>
<string name="qr_code_login_confirm_security_code">Confirm</string>
<string name="qr_code_login_confirm_security_code_description">Please ensure that you know the origin of this code. By linking devices, you will provide someone with full access to your account.</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_scan_qr_code_title">Scan QR code</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_scan_qr_code_description">Use the camera on this device to scan the QR code shown on your other device:</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_show_qr_code_title">Sign in with QR code</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_show_qr_code_new_device_description">Use your signed in device to scan the QR code below:</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_show_qr_code_link_a_device_description">Scan the QR code below with your device thats signed out.</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_connected_title">Secure connection established</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_connected_description">Check your signed in device, the code below should be displayed. Confirm that the code below matches with that device:</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_title">Unsuccessful connection</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_device_is_not_supported_description">Linking with this device is not supported.</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_timeout_description">The linking wasnt completed in the required time.</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_denied_description">The request was denied on the other device.</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_other_description">The request failed.</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_e2ee_security_issue_description">A security issue was encountered setting up secure messaging. One of the following may be compromised: Your homeserver; Your internet connection(s); Your device(s);</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_other_device_already_signed_in_description">The other device is already signed in.</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_other_device_not_signed_in_description">The other device must be signed in.</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_invalid_qr_code_description">That QR code is invalid.</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_user_cancelled_description">The sign in was cancelled on the other device.</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_homeserver_is_not_supported_description">The homeserver doesn\'t support sign in with QR code.</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_new_device_instruction_1">Open the app on your other device</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_new_device_instruction_2">Go to Settings -> Security &amp; Privacy</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_new_device_instruction_3">Select \'Show QR code\'</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_link_a_device_scan_qr_code_instruction_1">Start at the sign in screen</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_link_a_device_scan_qr_code_instruction_2">Select \'Sign in with QR code\'</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_link_a_device_show_qr_code_instruction_1">Start at the sign in screen</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_link_a_device_show_qr_code_instruction_2">Select \'Scan QR code\'</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_show_qr_code_button">Show QR code in this device</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_signing_in_a_mobile_device">Signing in a mobile device?</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_scan_qr_code_button">Scan QR code</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_connecting_to_device">Connecting to device</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_signing_in">Signing you in</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_status_no_match">No match?</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_try_again">Try again</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_confirm_security_code">Confirm</string>
<!-- TODO TO BE REMOVED -->
<string tools:ignore="UnusedResources" name="qr_code_login_confirm_security_code_description">Please ensure that you know the origin of this code. By linking devices, you will provide someone with full access to your account.</string>
<!-- Rich text editor -->
<string name="rich_text_editor_format_bold">Apply bold format</string>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="QrCodeLoginInstructionsView">
<attr name="qrCodeLoginInstruction1" format="string" />
<attr name="qrCodeLoginInstruction2" format="string" />
<attr name="qrCodeLoginInstruction3" format="string" />
<attr name="qrCodeLoginInstruction4" format="string" />
</declare-styleable>
</resources>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="QrCodeLoginHeaderView">
<attr name="qrCodeLoginHeaderTitle" format="string" />
<attr name="qrCodeLoginHeaderDescription" format="string" />
<attr name="qrCodeLoginHeaderImageResource" format="reference" />
<attr name="qrCodeLoginHeaderImageBackgroundTint" format="color" />
</declare-styleable>
</resources>

View file

@ -62,7 +62,7 @@ android {
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
buildConfigField "String", "SDK_VERSION", "\"1.6.20\""
buildConfigField "String", "SDK_VERSION", "\"1.6.22\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
@ -205,9 +205,6 @@ dependencies {
// Work
implementation libs.androidx.work
// olm lib is now hosted in MavenCentral
implementation 'org.matrix.android:olm-sdk:3.2.12'
// DI
implementation libs.dagger.dagger
kapt libs.dagger.daggerCompiler
@ -224,7 +221,7 @@ dependencies {
implementation libs.google.phonenumber
implementation("org.matrix.rustcomponents:crypto-android:0.4.1")
implementation("org.matrix.rustcomponents:crypto-android:0.4.3")
// api project(":library:rustCrypto")
testImplementation libs.tests.junit
@ -236,6 +233,7 @@ dependencies {
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
// Transitively required for mocking realm as monarchy doesn't expose Rx
testImplementation libs.rx.rxKotlin
testImplementation libs.tests.robolectric
kaptAndroidTest libs.dagger.daggerCompiler
androidTestImplementation libs.androidx.testCore

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a7acd69f37612bab0a1ab7f456656712d7ba19dbb679f81b97b58ef44e239f42
size 8523776

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:59b4957aa2f9cdc17b14ec8546e144537fac9dee050c6eb173f56fa8602c2736
size 2097152

View file

@ -1,110 +0,0 @@
/*
* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous
import org.amshove.kluent.invoking
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldBeInstanceOf
import org.amshove.kluent.shouldThrow
import org.amshove.kluent.with
import org.junit.Test
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.rendezvous.channels.ECDHRendezvousChannel
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
import org.matrix.android.sdk.common.CommonTestHelper
class RendezvousTest : InstrumentedTest {
@Test
fun shouldSuccessfullyBuildChannels() = CommonTestHelper.runCryptoTest(context()) { _, _ ->
val cases = listOf(
// v1:
"{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256\"," +
"\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" +
"{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," +
"\"intent\":\"login.reciprocate\"}",
// v2:
"{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256\"," +
"\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" +
"{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," +
"\"intent\":\"login.reciprocate\"}",
)
cases.forEach { input ->
Rendezvous.buildChannelFromCode(input).channel shouldBeInstanceOf ECDHRendezvousChannel::class
}
}
@Test
fun shouldFailToBuildChannelAsUnsupportedAlgorithm() {
invoking {
Rendezvous.buildChannelFromCode(
"{\"rendezvous\":{\"algorithm\":\"bad algo\"," +
"\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" +
"{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," +
"\"intent\":\"login.reciprocate\"}"
)
} shouldThrow RendezvousError::class with {
this.reason shouldBeEqualTo RendezvousFailureReason.UnsupportedAlgorithm
}
}
@Test
fun shouldFailToBuildChannelAsUnsupportedTransport() {
invoking {
Rendezvous.buildChannelFromCode(
"{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256\"," +
"\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" +
"{\"type\":\"bad transport\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," +
"\"intent\":\"login.reciprocate\"}"
)
} shouldThrow RendezvousError::class with {
this.reason shouldBeEqualTo RendezvousFailureReason.UnsupportedTransport
}
}
@Test
fun shouldFailToBuildChannelWithInvalidIntent() {
invoking {
Rendezvous.buildChannelFromCode(
"{\"rendezvous\":{\"algorithm\":\"org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256\"," +
"\"key\":\"aeSGwYTV1IUhikUyCapzC6p2xG5NpJ4Lwj2UgUMlcTk\",\"transport\":" +
"{\"type\":\"org.matrix.msc3886.http.v1\",\"uri\":\"https://rendezvous.lab.element.dev/bcab62cd-3e34-48b4-bc39-90895da8f6fe\"}}," +
"\"intent\":\"foo\"}"
)
} shouldThrow RendezvousError::class with {
this.reason shouldBeEqualTo RendezvousFailureReason.InvalidCode
}
}
@Test
fun shouldFailToBuildChannelAsInvalidCode() {
val cases = listOf(
"{}",
"rubbish",
""
)
cases.forEach { input ->
invoking {
Rendezvous.buildChannelFromCode(input)
} shouldThrow RendezvousError::class with {
this.reason shouldBeEqualTo RendezvousFailureReason.InvalidCode
}
}
}
}

View file

@ -36,7 +36,6 @@ import org.matrix.android.sdk.internal.network.ApiInterceptor
import org.matrix.android.sdk.internal.network.UserAgentHolder
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
import org.matrix.olm.OlmManager
import java.util.concurrent.Executors
import javax.inject.Inject
@ -49,7 +48,6 @@ internal class TestMatrix(context: Context, matrixConfiguration: MatrixConfigura
@Inject internal lateinit var rawService: RawService
@Inject internal lateinit var userAgentHolder: UserAgentHolder
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
@Inject internal lateinit var olmManager: OlmManager
@Inject internal lateinit var sessionManager: SessionManager
@Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
@Inject internal lateinit var apiInterceptor: ApiInterceptor

View file

@ -20,7 +20,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
@ -29,19 +28,12 @@ import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.common.assertByteArrayNotEqual
import org.matrix.olm.OlmManager
import org.matrix.olm.OlmPkDecryption
@Ignore("Ignored in order to speed up test run time")
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class KeysBackupPasswordTest : InstrumentedTest {
@Before
fun ensureLibLoaded() {
OlmManager()
}
/**
* Check KeysBackupPassword utilities
*/
@ -51,7 +43,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
assertEquals(32, generatePrivateKeyResult.salt.length)
assertEquals(500_000, generatePrivateKeyResult.iterations)
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, generatePrivateKeyResult.privateKey.size)
// Reverse operation
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
@ -60,7 +52,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
generatePrivateKeyResult.iterations
)
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, retrievedPrivateKey.size)
assertArrayEquals(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
}
@ -101,7 +93,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
assertEquals(32, generatePrivateKeyResult.salt.length)
assertEquals(500_000, generatePrivateKeyResult.iterations)
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, generatePrivateKeyResult.privateKey.size)
// Reverse operation, with bad password
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
@ -110,7 +102,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
generatePrivateKeyResult.iterations
)
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, retrievedPrivateKey.size)
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
}
@ -123,7 +115,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
assertEquals(32, generatePrivateKeyResult.salt.length)
assertEquals(500_000, generatePrivateKeyResult.iterations)
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, generatePrivateKeyResult.privateKey.size)
// Reverse operation, with bad iteration
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
@ -132,7 +124,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
500_001
)
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, retrievedPrivateKey.size)
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
}
@ -145,7 +137,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
assertEquals(32, generatePrivateKeyResult.salt.length)
assertEquals(500_000, generatePrivateKeyResult.iterations)
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, generatePrivateKeyResult.privateKey.size)
// Reverse operation, with bad iteration
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
@ -154,7 +146,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
generatePrivateKeyResult.iterations
)
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, retrievedPrivateKey.size)
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
}
@ -169,7 +161,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
val retrievedPrivateKey = retrievePrivateKeyWithPassword(password, salt, iteration)
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, retrievedPrivateKey.size)
// Data from RiotWeb
val privateKeyBytes = byteArrayOf(
@ -187,5 +179,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
private const val BAD_PASSWORD = "passw0rd"
private const val BAD_SALT = "AA0lxhQ9aYgGfMsclVWPIAublg8h9Nlu"
private const val EXPECTED_PRIVATE_KEY_LENGTH = 32
}
}

View file

@ -1,147 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.migration
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import io.mockk.spyk
import io.realm.Realm
import io.realm.kotlin.where
import org.amshove.kluent.internal.assertEquals
import org.junit.After
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.matrix.android.sdk.TestBuildVersionSdkIntProvider
import org.matrix.android.sdk.api.securestorage.SecretStoringUtils
import org.matrix.android.sdk.internal.crypto.RustEncryptionConfiguration
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
import org.matrix.android.sdk.internal.crypto.store.db.RustMigrationInfoProvider
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntity
import org.matrix.android.sdk.internal.database.RealmKeysUtils
import org.matrix.android.sdk.internal.database.TestRealmConfigurationFactory
import org.matrix.android.sdk.internal.util.time.Clock
import org.matrix.android.sdk.test.shared.createTimberTestRule
import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmManager
import org.matrix.rustcomponents.sdk.crypto.OlmMachine
import java.io.File
import java.security.KeyStore
@RunWith(AndroidJUnit4::class)
class DynamicElementAndroidToElementRMigrationTest {
@get:Rule val configurationFactory = TestRealmConfigurationFactory()
@Rule
fun timberTestRule() = createTimberTestRule()
var context: Context = InstrumentationRegistry.getInstrumentation().context
var realm: Realm? = null
@Before
fun setUp() {
// Ensure Olm is initialized
OlmManager()
}
@After
fun tearDown() {
realm?.close()
}
private val keyStore = spyk(KeyStore.getInstance("AndroidKeyStore")).also { it.load(null) }
private val rustEncryptionConfiguration = RustEncryptionConfiguration(
"foo",
RealmKeysUtils(
context,
SecretStoringUtils(context, keyStore, TestBuildVersionSdkIntProvider(), false)
)
)
private val fakeClock = object : Clock {
override fun epochMillis() = 0L
}
@Test
fun given_a_valid_crypto_store_realm_file_then_migration_should_be_successful() {
testMigrate(false)
}
@Test
@Ignore("We don't migrate group sessions for now, and it's making this test suite unstable")
fun given_a_valid_crypto_store_realm_file_no_lazy_then_migration_should_be_successful() {
testMigrate(true)
}
private fun testMigrate(migrateGroupSessions: Boolean) {
val targetFile = File(configurationFactory.root, "rust-sdk")
val realmName = "crypto_store_migration_16.realm"
val infoProvider = RustMigrationInfoProvider(
targetFile,
rustEncryptionConfiguration
).apply {
migrateMegolmGroupSessions = migrateGroupSessions
}
val migration = RealmCryptoStoreMigration(fakeClock, infoProvider)
val realmConfiguration = configurationFactory.createConfiguration(
realmName,
null,
RealmCryptoStoreModule(),
migration.schemaVersion,
migration
)
configurationFactory.copyRealmFromAssets(context, realmName, realmName)
realm = Realm.getInstance(realmConfiguration)
val metaData = realm!!.where<CryptoMetadataEntity>().findFirst()!!
val userId = metaData.userId!!
val deviceId = metaData.deviceId!!
val olmAccount = metaData.getOlmAccount()!!
val machine = OlmMachine(userId, deviceId, targetFile.path, rustEncryptionConfiguration.getDatabasePassphrase())
assertEquals(olmAccount.identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY], machine.identityKeys()["ed25519"])
assertNotNull(machine.getBackupKeys())
val crossSigningStatus = machine.crossSigningStatus()
assertTrue(crossSigningStatus.hasMaster)
assertTrue(crossSigningStatus.hasSelfSigning)
assertTrue(crossSigningStatus.hasUserSigning)
if (migrateGroupSessions) {
assertTrue("Some outbound sessions should be migrated", machine.roomKeyCounts().total.toInt() > 0)
assertTrue("There are some backed-up sessions", machine.roomKeyCounts().backedUp.toInt() > 0)
} else {
assertTrue(machine.roomKeyCounts().total.toInt() == 0)
assertTrue(machine.roomKeyCounts().backedUp.toInt() == 0)
}
// legacy olm sessions should have been deleted
val remainingOlmSessions = realm!!.where<OlmSessionEntity>().findAll().size
assertEquals("legacy olm sessions should have been removed from store", 0, remainingOlmSessions)
}
}

View file

@ -1,92 +0,0 @@
/*
* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* 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.database
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import io.mockk.spyk
import io.realm.Realm
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.matrix.android.sdk.TestBuildVersionSdkIntProvider
import org.matrix.android.sdk.api.securestorage.SecretStoringUtils
import org.matrix.android.sdk.internal.crypto.RustEncryptionConfiguration
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
import org.matrix.android.sdk.internal.crypto.store.db.RustMigrationInfoProvider
import org.matrix.android.sdk.internal.util.time.Clock
import org.matrix.olm.OlmManager
import java.io.File
import java.security.KeyStore
class CryptoSanityMigrationTest {
@get:Rule val configurationFactory = TestRealmConfigurationFactory()
lateinit var context: Context
var realm: Realm? = null
@Before
fun setUp() {
// Ensure Olm is initialized
OlmManager()
context = InstrumentationRegistry.getInstrumentation().context
}
@After
fun tearDown() {
realm?.close()
}
private val keyStore = spyk(KeyStore.getInstance("AndroidKeyStore")).also { it.load(null) }
@Test
fun cryptoDatabaseShouldMigrateGracefully() {
val realmName = "crypto_store_20.realm"
val rustMigrationInfo = RustMigrationInfoProvider(
File(configurationFactory.root, "test_rust"),
RustEncryptionConfiguration(
"foo",
RealmKeysUtils(
context,
SecretStoringUtils(context, keyStore, TestBuildVersionSdkIntProvider(), false)
)
),
)
val migration = RealmCryptoStoreMigration(
object : Clock {
override fun epochMillis(): Long {
return 0L
}
},
rustMigrationInfo
)
val realmConfiguration = configurationFactory.createConfiguration(
realmName,
"7b9a21a8a311e85d75b069a343c23fc952fc3fec5e0c83ecfa13f24b787479c487c3ed587db3dd1f5805d52041fc0ac246516e94b27ffa699ff928622e621aca",
RealmCryptoStoreModule(),
migration.schemaVersion,
migration
)
configurationFactory.copyRealmFromAssets(context, realmName, realmName)
realm = Realm.getInstance(realmConfiguration)
}
}

View file

@ -0,0 +1,121 @@
/*
* Copyright (c) 2024 The Matrix.org Foundation C.I.C.
*
* 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.session.contentscanner
import org.amshove.kluent.shouldBe
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldNotBe
import org.junit.Test
import org.matrix.android.sdk.api.session.crypto.attachments.ElementToDecrypt
import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileInfo
import org.matrix.android.sdk.api.session.crypto.model.EncryptedFileKey
import org.matrix.android.sdk.internal.session.contentscanner.model.DownloadBody
class ScanEncryptorUtilsTest {
private val anMxcUrl = "mxc://matrix.org/123456"
private val anElementToDecrypt = ElementToDecrypt(
k = "key",
iv = "iv",
sha256 = "sha256"
)
private val aPublicKey = "6n3l15JqsNhpM1OwRIoDCL/3c1B5idcwvy07Y5qFRyw="
private val aPrivateKey = "CLYwNaeA9d0KHE0DniO1bxGgmNsPJ/pyanF4b4tcK1M="
@Test
fun whenNoServerKeyIsProvidedTheContentIsNotEncrypted() {
val result = ScanEncryptorUtils.getDownloadBodyAndEncryptIfNeeded(
publicServerKey = null,
mxcUrl = anMxcUrl,
elementToDecrypt = anElementToDecrypt
)
result shouldBeEqualTo DownloadBody(
file = EncryptedFileInfo(
url = anMxcUrl,
iv = anElementToDecrypt.iv,
hashes = mapOf("sha256" to anElementToDecrypt.sha256),
key = EncryptedFileKey(
k = anElementToDecrypt.k,
alg = "A256CTR",
keyOps = listOf("encrypt", "decrypt"),
kty = "oct",
ext = true
),
v = "v2"
),
encryptedBody = null
)
}
@Test
fun whenServerKeyIsProvidedTheContentIsEncrypted() {
val result = ScanEncryptorUtils.getDownloadBodyAndEncryptIfNeeded(
publicServerKey = aPublicKey,
mxcUrl = anMxcUrl,
elementToDecrypt = anElementToDecrypt
)
result.file shouldBe null
// Note: we cannot check the members of EncryptedBody because they change on each call.
result.encryptedBody shouldNotBe null
}
// Note: PkDecryption is not exposed in the FFI layer, so we cannot use this test.
/*
@Test
fun checkThatTheCodeIsAbleToDecryptContent() {
System.loadLibrary("olm")
val clearInfo = ScanEncryptorUtils.getDownloadBodyAndEncryptIfNeeded(
publicServerKey = null,
mxcUrl = anMxcUrl,
elementToDecrypt = anElementToDecrypt
)
// Uncomment to get a new encrypted body
// val encryptedBody = ScanEncryptorUtils.getDownloadBodyAndEncryptIfNeeded(
// publicServerKey = aPublicKey,
// mxcUrl = anMxcUrl,
// elementToDecrypt = anElementToDecrypt
// ).encryptedBody!!
// println("libolmEncryptedBody: $encryptedBody")
val libolmEncryptedBody = EncryptedBody(
cipherText = "GTnDhm6xe5fPe/QCr6fyGcZXheFhZlPG" +
"nJZiCK8Xwq6qTg71vSUGWtLdt3uaTmK7" +
"F7fB3PBKchHu2VVv6MMgo8fpUQ7KBbmu" +
"NWTrNmf3QdhXuRwUwz/q4GxsbGR2zjSX" +
"/UoE5S4ymVtOVhvSfXQfssN56wVIzC6S" +
"dy57y6b1IXPihlCUdvb8LMkMvViHYeNf" +
"beFrAfMlsyr1+jdZEXZF5Q7iruhsH2iu" +
"k7+Ayl9rdILCD5tjE9pezwe1V6uc/Agb",
mac = "Wk77HRg50oM",
ephemeral = "rMTK6/CGASinfX4USFS5qmD3r4meffxKc/jCSFIBczw"
)
// Try to decrypt the body
val result = withOlmDecryption { olmPkDecryption ->
olmPkDecryption.setPrivateKey(aPrivateKey.decodeBase64()!!.toByteArray())
olmPkDecryption.decrypt(
OlmPkMessage().apply {
mCipherText = libolmEncryptedBody.cipherText
mMac = libolmEncryptedBody.mac
mEphemeralKey = libolmEncryptedBody.ephemeral
}
)
}
val parseResult = MoshiProvider.providesMoshi()
.adapter(DownloadBody::class.java)
.fromJson(result)
parseResult shouldBeEqualTo clearInfo
}
*/
}

View file

@ -39,7 +39,6 @@ import org.matrix.android.sdk.internal.network.ApiInterceptor
import org.matrix.android.sdk.internal.network.UserAgentHolder
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
import org.matrix.olm.OlmManager
import java.util.concurrent.Executors
import javax.inject.Inject
@ -59,7 +58,6 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) {
@Inject internal lateinit var debugService: DebugService
@Inject internal lateinit var userAgentHolder: UserAgentHolder
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
@Inject internal lateinit var olmManager: OlmManager
@Inject internal lateinit var sessionManager: SessionManager
@Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
@Inject internal lateinit var apiInterceptor: ApiInterceptor

View file

@ -31,11 +31,6 @@ const val MXCRYPTO_ALGORITHM_MEGOLM = "m.megolm.v1.aes-sha2"
*/
const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes-sha2"
/**
* Secured Shared Storage algorithm constant.
*/
const val SSSS_ALGORITHM_CURVE25519_AES_SHA2 = "m.secret_storage.v1.curve25519-aes-sha2"
/* Secrets are encrypted using AES-CTR-256 and MACed using HMAC-SHA-256. **/
const val SSSS_ALGORITHM_AES_HMAC_SHA2 = "m.secret_storage.v1.aes-hmac-sha2"

View file

@ -1,254 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous
import android.net.Uri
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.rendezvous.channels.ECDHRendezvousChannel
import org.matrix.android.sdk.api.rendezvous.model.ECDHRendezvousCode
import org.matrix.android.sdk.api.rendezvous.model.Outcome
import org.matrix.android.sdk.api.rendezvous.model.Payload
import org.matrix.android.sdk.api.rendezvous.model.PayloadType
import org.matrix.android.sdk.api.rendezvous.model.Protocol
import org.matrix.android.sdk.api.rendezvous.model.RendezvousCode
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
import org.matrix.android.sdk.api.rendezvous.model.RendezvousIntent
import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportType
import org.matrix.android.sdk.api.rendezvous.model.SecureRendezvousChannelAlgorithm
import org.matrix.android.sdk.api.rendezvous.transports.SimpleHttpRendezvousTransport
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.util.MatrixJsonParser
import timber.log.Timber
// n.b MSC3886/MSC3903/MSC3906 that this is based on are now closed.
// However, we want to keep this implementation around for some time.
// TODO define an end-of-life date for this implementation.
/**
* Implementation of MSC3906 to sign in + E2EE set up using a QR code.
*/
class Rendezvous(
val channel: RendezvousChannel,
val theirIntent: RendezvousIntent,
) {
companion object {
private val TAG = LoggerTag(Rendezvous::class.java.simpleName, LoggerTag.RENDEZVOUS).value
@Throws(RendezvousError::class)
fun buildChannelFromCode(code: String): Rendezvous {
// we first check that the code is valid JSON and has right high-level structure
val genericParsed = try {
// we rely on moshi validating the code and throwing exception if invalid JSON or algorithm doesn't match
MatrixJsonParser.getMoshi().adapter(RendezvousCode::class.java).fromJson(code)
} catch (a: Throwable) {
throw RendezvousError("Malformed code", RendezvousFailureReason.InvalidCode)
} ?: throw RendezvousError("Code is null", RendezvousFailureReason.InvalidCode)
// then we check that algorithm is supported
if (!SecureRendezvousChannelAlgorithm.values().map { it.value }.contains(genericParsed.rendezvous.algorithm)) {
throw RendezvousError("Unsupported algorithm", RendezvousFailureReason.UnsupportedAlgorithm)
}
// and, that the transport is supported
if (!RendezvousTransportType.values().map { it.value }.contains(genericParsed.rendezvous.transport.type)) {
throw RendezvousError("Unsupported transport", RendezvousFailureReason.UnsupportedTransport)
}
// now that we know the overall structure looks sensible, we rely on moshi validating the code and
// throwing exception if other parts are invalid
val supportedParsed = try {
MatrixJsonParser.getMoshi().adapter(ECDHRendezvousCode::class.java).fromJson(code)
} catch (a: Throwable) {
throw RendezvousError("Malformed ECDH rendezvous code", RendezvousFailureReason.InvalidCode)
} ?: throw RendezvousError("ECDH rendezvous code is null", RendezvousFailureReason.InvalidCode)
val transport = SimpleHttpRendezvousTransport(supportedParsed.rendezvous.transport.uri)
return Rendezvous(
ECDHRendezvousChannel(transport, supportedParsed.rendezvous.algorithm, supportedParsed.rendezvous.key),
supportedParsed.intent
)
}
}
private val adapter = MatrixJsonParser.getMoshi().adapter(Payload::class.java)
// not yet implemented: RendezvousIntent.RECIPROCATE_LOGIN_ON_EXISTING_DEVICE
val ourIntent: RendezvousIntent = RendezvousIntent.LOGIN_ON_NEW_DEVICE
@Throws(RendezvousError::class)
private suspend fun checkCompatibility() {
val incompatible = theirIntent == ourIntent
Timber.tag(TAG).d("ourIntent: $ourIntent, theirIntent: $theirIntent, incompatible: $incompatible")
if (incompatible) {
// inform the other side
send(Payload(PayloadType.FINISH, intent = ourIntent))
if (ourIntent == RendezvousIntent.LOGIN_ON_NEW_DEVICE) {
throw RendezvousError("The other device isn't signed in", RendezvousFailureReason.OtherDeviceNotSignedIn)
} else {
throw RendezvousError("The other device is already signed in", RendezvousFailureReason.OtherDeviceAlreadySignedIn)
}
}
}
@Throws(RendezvousError::class)
suspend fun startAfterScanningCode(): String {
val checksum = channel.connect()
Timber.tag(TAG).i("Connected to secure channel with checksum: $checksum")
checkCompatibility()
// get protocols
Timber.tag(TAG).i("Waiting for protocols")
val protocolsResponse = receive()
if (protocolsResponse?.protocols == null || !protocolsResponse.protocols.contains(Protocol.LOGIN_TOKEN)) {
send(Payload(PayloadType.FINISH, outcome = Outcome.UNSUPPORTED))
throw RendezvousError("Unsupported protocols", RendezvousFailureReason.UnsupportedHomeserver)
}
send(Payload(PayloadType.PROGRESS, protocol = Protocol.LOGIN_TOKEN))
return checksum
}
@Throws(RendezvousError::class)
suspend fun waitForLoginOnNewDevice(authenticationService: AuthenticationService): Session {
Timber.tag(TAG).i("Waiting for login_token")
val loginToken = receive()
if (loginToken?.type == PayloadType.FINISH) {
when (loginToken.outcome) {
Outcome.DECLINED -> {
throw RendezvousError("Login declined by other device", RendezvousFailureReason.UserDeclined)
}
Outcome.UNSUPPORTED -> {
throw RendezvousError("Homeserver lacks support", RendezvousFailureReason.UnsupportedHomeserver)
}
else -> {
throw RendezvousError("Unknown error", RendezvousFailureReason.Unknown)
}
}
}
val homeserver = loginToken?.homeserver ?: throw RendezvousError("No homeserver returned", RendezvousFailureReason.ProtocolError)
val token = loginToken.loginToken ?: throw RendezvousError("No login token returned", RendezvousFailureReason.ProtocolError)
Timber.tag(TAG).i("Got login_token now attempting to sign in with $homeserver")
val hsConfig = HomeServerConnectionConfig(homeServerUri = Uri.parse(homeserver))
return authenticationService.loginUsingQrLoginToken(hsConfig, token)
}
@Throws(RendezvousError::class)
suspend fun completeVerificationOnNewDevice(session: Session) {
val userId = session.myUserId
val crypto = session.cryptoService()
val deviceId = crypto.getMyCryptoDevice().deviceId
val deviceKey = crypto.getMyCryptoDevice().fingerprint()
send(Payload(PayloadType.PROGRESS, outcome = Outcome.SUCCESS, deviceId = deviceId, deviceKey = deviceKey))
try {
// explicitly download keys for ourself rather than racing with initial sync which might not complete in time
crypto.downloadKeysIfNeeded(listOf(userId), false)
} catch (e: Throwable) {
// log as warning and continue as initial sync might still complete
Timber.tag(TAG).w(e, "Failed to download keys for self")
}
// await confirmation of verification
val verificationResponse = receive()
if (verificationResponse?.outcome == Outcome.VERIFIED) {
val verifyingDeviceId = verificationResponse.verifyingDeviceId
?: throw RendezvousError("No verifying device id returned", RendezvousFailureReason.ProtocolError)
val verifyingDeviceFromServer = crypto.getCryptoDeviceInfo(userId, verifyingDeviceId)
if (verifyingDeviceFromServer?.fingerprint() != verificationResponse.verifyingDeviceKey) {
Timber.tag(TAG).w(
"Verifying device $verifyingDeviceId key doesn't match: ${
verifyingDeviceFromServer?.fingerprint()
} vs ${verificationResponse.verifyingDeviceKey})"
)
// inform the other side
send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR))
throw RendezvousError("Key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue)
}
verificationResponse.masterKey?.let { masterKeyFromVerifyingDevice ->
// verifying device provided us with a master key, so use it to check integrity
// see what the homeserver told us
val localMasterKey = crypto.crossSigningService().getMyCrossSigningKeys()?.masterKey()
// n.b. if no local master key this is a problem, as well as it not matching
if (localMasterKey?.unpaddedBase64PublicKey != masterKeyFromVerifyingDevice) {
Timber.tag(TAG).w("Master key from verifying device doesn't match: $masterKeyFromVerifyingDevice vs $localMasterKey")
// inform the other side
send(Payload(PayloadType.FINISH, outcome = Outcome.E2EE_SECURITY_ERROR))
throw RendezvousError("Master key from verifying device doesn't match", RendezvousFailureReason.E2EESecurityIssue)
}
// set other device as verified
Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified")
crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId)
Timber.tag(TAG).i("Setting master key as trusted")
crypto.crossSigningService().markMyMasterKeyAsTrusted()
} ?: run {
// set other device as verified anyway
Timber.tag(TAG).i("Setting device $verifyingDeviceId as verified")
crypto.setDeviceVerification(DeviceTrustLevel(locallyVerified = true, crossSigningVerified = false), userId, verifyingDeviceId)
Timber.tag(TAG).i("No master key given by verifying device")
}
// request secrets from other sessions.
Timber.tag(TAG).i("Requesting secrets from other sessions")
session.sharedSecretStorageService().requestMissingSecrets()
} else {
Timber.tag(TAG).i("Not doing verification")
}
}
@Throws(RendezvousError::class)
private suspend fun receive(): Payload? {
val data = channel.receive() ?: return null
val payload = try {
adapter.fromJson(data.toString(Charsets.UTF_8))
} catch (e: Exception) {
Timber.tag(TAG).w(e, "Failed to parse payload")
throw RendezvousError("Invalid payload received", RendezvousFailureReason.Unknown)
}
return payload
}
private suspend fun send(payload: Payload) {
channel.send(adapter.toJson(payload).toByteArray(Charsets.UTF_8))
}
suspend fun close() {
channel.close()
}
}

View file

@ -1,51 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
/**
* Representation of a rendezvous channel such as that described by MSC3903.
*/
interface RendezvousChannel {
val transport: RendezvousTransport
/**
* @returns the checksum/confirmation digits to be shown to the user
*/
@Throws(RendezvousError::class)
suspend fun connect(): String
/**
* Send a payload via the channel.
* @param data payload to send
*/
@Throws(RendezvousError::class)
suspend fun send(data: ByteArray)
/**
* Receive a payload from the channel.
* @returns the received payload
*/
@Throws(RendezvousError::class)
suspend fun receive(): ByteArray?
/**
* Closes the channel and cleans up.
*/
suspend fun close()
}

View file

@ -1,32 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous
enum class RendezvousFailureReason(val canRetry: Boolean = true) {
UserDeclined,
OtherDeviceNotSignedIn,
OtherDeviceAlreadySignedIn,
Unknown,
Expired,
UserCancelled,
InvalidCode,
UnsupportedAlgorithm(false),
UnsupportedTransport(false),
UnsupportedHomeserver(false),
ProtocolError,
E2EESecurityIssue(false)
}

View file

@ -1,36 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous
import okhttp3.MediaType
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails
interface RendezvousTransport {
var ready: Boolean
@Throws(RendezvousError::class)
suspend fun details(): RendezvousTransportDetails
@Throws(RendezvousError::class)
suspend fun send(contentType: MediaType, data: ByteArray)
@Throws(RendezvousError::class)
suspend fun receive(): ByteArray?
suspend fun close()
}

View file

@ -1,199 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.channels
import android.util.Base64
import com.squareup.moshi.JsonClass
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import okhttp3.MediaType.Companion.toMediaType
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.rendezvous.RendezvousChannel
import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
import org.matrix.android.sdk.api.rendezvous.RendezvousTransport
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
import org.matrix.android.sdk.api.rendezvous.model.SecureRendezvousChannelAlgorithm
import org.matrix.android.sdk.api.util.MatrixJsonParser
import org.matrix.android.sdk.internal.crypto.verification.getDecimalCodeRepresentation
import org.matrix.olm.OlmSAS
import timber.log.Timber
import java.security.SecureRandom
import java.util.LinkedList
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
/**
* Implements X25519 ECDH key agreement and AES-256-GCM encryption channel as per MSC3903:
* https://github.com/matrix-org/matrix-spec-proposals/pull/3903
*/
class ECDHRendezvousChannel(
override var transport: RendezvousTransport,
private val algorithm: SecureRendezvousChannelAlgorithm,
theirPublicKeyBase64: String?,
) : RendezvousChannel {
companion object {
private const val ALGORITHM_SPEC = "AES/GCM/NoPadding"
private const val KEY_SPEC = "AES"
private val TAG = LoggerTag(ECDHRendezvousChannel::class.java.simpleName, LoggerTag.RENDEZVOUS).value
}
@JsonClass(generateAdapter = true)
internal data class ECDHPayload(
val algorithm: SecureRendezvousChannelAlgorithm? = null,
val key: String? = null,
val ciphertext: String? = null,
val iv: String? = null,
)
private val olmSASMutex = Mutex()
private var olmSAS: OlmSAS?
private val ourPublicKey: ByteArray
private val ecdhAdapter = MatrixJsonParser.getMoshi().adapter(ECDHPayload::class.java)
private var theirPublicKey: ByteArray? = null
private var aesKey: ByteArray? = null
init {
theirPublicKeyBase64?.let {
theirPublicKey = decodeBase64(it)
}
olmSAS = OlmSAS()
ourPublicKey = decodeBase64(olmSAS!!.publicKey)
}
fun encodeBase64(input: ByteArray?): String? {
if (algorithm == SecureRendezvousChannelAlgorithm.ECDH_V2) {
return Base64.encodeToString(input, Base64.NO_WRAP or Base64.NO_PADDING)
}
return Base64.encodeToString(input, Base64.NO_WRAP)
}
fun decodeBase64(input: String?): ByteArray {
// for decoding we aren't concerned about padding
return Base64.decode(input, Base64.NO_WRAP)
}
@Throws(RendezvousError::class)
override suspend fun connect(): String {
val sas = olmSAS ?: throw RendezvousError("Channel closed", RendezvousFailureReason.Unknown)
val isInitiator = theirPublicKey == null
if (isInitiator) {
Timber.tag(TAG).i("Waiting for other device to send their public key")
val res = this.receiveAsPayload() ?: throw RendezvousError("No reply from other device", RendezvousFailureReason.ProtocolError)
if (res.key == null) {
throw RendezvousError(
"Unsupported algorithm: ${res.algorithm}",
RendezvousFailureReason.UnsupportedAlgorithm,
)
}
theirPublicKey = decodeBase64(res.key)
} else {
// send our public key unencrypted
Timber.tag(TAG).i("Sending public key")
send(
ECDHPayload(
algorithm = algorithm,
key = encodeBase64(ourPublicKey)
)
)
}
olmSASMutex.withLock {
sas.setTheirPublicKey(encodeBase64(theirPublicKey))
sas.setTheirPublicKey(encodeBase64(theirPublicKey))
val initiatorKey = encodeBase64(if (isInitiator) ourPublicKey else theirPublicKey)
val recipientKey = encodeBase64(if (isInitiator) theirPublicKey else ourPublicKey)
val aesInfo = "${algorithm.value}|$initiatorKey|$recipientKey"
aesKey = sas.generateShortCode(aesInfo, 32)
val rawChecksum = sas.generateShortCode(aesInfo, 5)
return rawChecksum.getDecimalCodeRepresentation(separator = "-")
}
}
private suspend fun send(payload: ECDHPayload) {
transport.send("application/json".toMediaType(), ecdhAdapter.toJson(payload).toByteArray(Charsets.UTF_8))
}
override suspend fun send(data: ByteArray) {
if (aesKey == null) {
throw IllegalStateException("Shared secret not established")
}
send(encrypt(data))
}
private suspend fun receiveAsPayload(): ECDHPayload? {
transport.receive()?.toString(Charsets.UTF_8)?.let {
return ecdhAdapter.fromJson(it)
} ?: return null
}
override suspend fun receive(): ByteArray? {
if (aesKey == null) {
throw IllegalStateException("Shared secret not established")
}
val payload = receiveAsPayload() ?: return null
return decrypt(payload)
}
override suspend fun close() {
val sas = olmSAS ?: throw IllegalStateException("Channel already closed")
olmSASMutex.withLock {
// this does a double release check already so we don't re-check ourselves
sas.releaseSas()
olmSAS = null
}
transport.close()
}
private fun encrypt(plainText: ByteArray): ECDHPayload {
val iv = ByteArray(16)
SecureRandom().nextBytes(iv)
val cipherText = LinkedList<Byte>()
val encryptCipher = Cipher.getInstance(ALGORITHM_SPEC)
val secretKeySpec = SecretKeySpec(aesKey, KEY_SPEC)
val ivParameterSpec = IvParameterSpec(iv)
encryptCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec)
cipherText.addAll(encryptCipher.update(plainText).toList())
cipherText.addAll(encryptCipher.doFinal().toList())
return ECDHPayload(
ciphertext = encodeBase64(cipherText.toByteArray()),
iv = encodeBase64(iv)
)
}
private fun decrypt(payload: ECDHPayload): ByteArray {
val iv = decodeBase64(payload.iv)
val encryptCipher = Cipher.getInstance(ALGORITHM_SPEC)
val secretKeySpec = SecretKeySpec(aesKey, KEY_SPEC)
val ivParameterSpec = IvParameterSpec(iv)
encryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec)
val plainText = LinkedList<Byte>()
plainText.addAll(encryptCipher.update(decodeBase64(payload.ciphertext)).toList())
plainText.addAll(encryptCipher.doFinal().toList())
return plainText.toByteArray()
}
}

View file

@ -1,26 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.model
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class ECDHRendezvous(
val transport: SimpleHttpRendezvousTransportDetails,
val algorithm: SecureRendezvousChannelAlgorithm,
val key: String
)

View file

@ -1,25 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.model
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class ECDHRendezvousCode(
val intent: RendezvousIntent,
val rendezvous: ECDHRendezvous
)

View file

@ -1,38 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class Outcome(val value: String) {
@Json(name = "success")
SUCCESS("success"),
@Json(name = "declined")
DECLINED("declined"),
@Json(name = "unsupported")
UNSUPPORTED("unsupported"),
@Json(name = "verified")
VERIFIED("verified"),
@Json(name = "e2ee_security_error")
E2EE_SECURITY_ERROR("e2ee_security_error")
}

View file

@ -1,36 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class Payload(
val type: PayloadType,
val intent: RendezvousIntent? = null,
val outcome: Outcome? = null,
val protocols: List<Protocol>? = null,
val protocol: Protocol? = null,
val homeserver: String? = null,
@Json(name = "login_token") val loginToken: String? = null,
@Json(name = "device_id") val deviceId: String? = null,
@Json(name = "device_key") val deviceKey: String? = null,
@Json(name = "verifying_device_id") val verifyingDeviceId: String? = null,
@Json(name = "verifying_device_key") val verifyingDeviceKey: String? = null,
@Json(name = "master_key") val masterKey: String? = null
)

View file

@ -1,32 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
internal enum class PayloadType(val value: String) {
@Json(name = "m.login.start")
START("m.login.start"),
@Json(name = "m.login.finish")
FINISH("m.login.finish"),
@Json(name = "m.login.progress")
PROGRESS("m.login.progress")
}

View file

@ -1,26 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class Protocol(val value: String) {
@Json(name = "org.matrix.msc3906.login_token")
LOGIN_TOKEN("org.matrix.msc3906.login_token")
}

View file

@ -1,25 +0,0 @@
/*
* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.model
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
open class Rendezvous(
val transport: RendezvousTransportDetails,
val algorithm: String,
)

View file

@ -1,25 +0,0 @@
/*
* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.model
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
open class RendezvousCode(
open val intent: RendezvousIntent,
open val rendezvous: Rendezvous
)

View file

@ -1,21 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.model
import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
class RendezvousError(val description: String, val reason: RendezvousFailureReason) : Exception(description)

View file

@ -1,26 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class RendezvousIntent {
@Json(name = "login.start") LOGIN_ON_NEW_DEVICE,
@Json(name = "login.reciprocate") RECIPROCATE_LOGIN_ON_EXISTING_DEVICE
}

View file

@ -1,24 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.model
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
open class RendezvousTransportDetails(
val type: String
)

View file

@ -1,26 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class RendezvousTransportType(val value: String) {
@Json(name = "org.matrix.msc3886.http.v1")
MSC3886_SIMPLE_HTTP_V1("org.matrix.msc3886.http.v1")
}

View file

@ -1,28 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class SecureRendezvousChannelAlgorithm(val value: String) {
@Json(name = "org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256")
ECDH_V1("org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256"),
@Json(name = "org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256")
ECDH_V2("org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256")
}

View file

@ -1,24 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.model
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class SimpleHttpRendezvousTransportDetails(
val uri: String
) : RendezvousTransportDetails(type = RendezvousTransportType.MSC3886_SIMPLE_HTTP_V1.name)

View file

@ -1,173 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.api.rendezvous.transports
import kotlinx.coroutines.delay
import okhttp3.MediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.rendezvous.RendezvousFailureReason
import org.matrix.android.sdk.api.rendezvous.RendezvousTransport
import org.matrix.android.sdk.api.rendezvous.model.RendezvousError
import org.matrix.android.sdk.api.rendezvous.model.RendezvousTransportDetails
import org.matrix.android.sdk.api.rendezvous.model.SimpleHttpRendezvousTransportDetails
import timber.log.Timber
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* Implementation of the Simple HTTP transport MSC3886: https://github.com/matrix-org/matrix-spec-proposals/pull/3886
*/
class SimpleHttpRendezvousTransport(rendezvousUri: String?) : RendezvousTransport {
companion object {
private val TAG = LoggerTag(SimpleHttpRendezvousTransport::class.java.simpleName, LoggerTag.RENDEZVOUS).value
}
override var ready = false
private var cancelled = false
private var uri: String?
private var etag: String? = null
private var expiresAt: Date? = null
init {
uri = rendezvousUri
}
override suspend fun details(): RendezvousTransportDetails {
val uri = uri ?: throw IllegalStateException("Rendezvous not set up")
return SimpleHttpRendezvousTransportDetails(uri)
}
@Throws(RendezvousError::class)
override suspend fun send(contentType: MediaType, data: ByteArray) {
if (cancelled) {
throw IllegalStateException("Rendezvous cancelled")
}
val method = if (uri != null) "PUT" else "POST"
val uri = this.uri ?: throw RuntimeException("No rendezvous URI")
val httpClient = okhttp3.OkHttpClient.Builder().build()
val request = Request.Builder()
.url(uri)
.method(method, data.toRequestBody())
.header("content-type", contentType.toString())
etag?.let {
request.header("if-match", it)
}
val response = httpClient.newCall(request.build()).execute()
if (response.code == 404) {
throw get404Error()
}
etag = response.header("etag")
Timber.tag(TAG).i("Sent data to $uri new etag $etag")
if (method == "POST") {
val location = response.header("location") ?: throw RuntimeException("No rendezvous URI found in response")
response.header("expires")?.let {
val format = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US)
expiresAt = format.parse(it)
}
// resolve location header which could be relative or absolute
this.uri = response.request.url.toUri().resolve(location).toString()
ready = true
}
}
@Throws(RendezvousError::class)
override suspend fun receive(): ByteArray? {
if (cancelled) {
throw IllegalStateException("Rendezvous cancelled")
}
val uri = uri ?: throw IllegalStateException("Rendezvous not set up")
val httpClient = okhttp3.OkHttpClient.Builder().build()
while (true) {
Timber.tag(TAG).i("Polling: $uri after etag $etag")
val request = Request.Builder()
.url(uri)
.get()
etag?.let {
request.header("if-none-match", it)
}
val response = httpClient.newCall(request.build()).execute()
try {
// expired
if (response.code == 404) {
throw get404Error()
}
// rely on server expiring the channel rather than checking ourselves
if (response.header("content-type") != "application/json") {
response.header("etag")?.let {
etag = it
}
} else if (response.code == 200) {
response.header("etag")?.let {
etag = it
}
return response.body?.bytes()
}
// sleep for a second before polling again
// we rely on the server expiring the channel rather than checking it ourselves
delay(1000)
} finally {
response.close()
}
}
}
private fun get404Error(): RendezvousError {
if (expiresAt != null && Date() > expiresAt) {
return RendezvousError("Expired", RendezvousFailureReason.Expired)
}
return RendezvousError("Received unexpected 404", RendezvousFailureReason.Unknown)
}
override suspend fun close() {
cancelled = true
ready = false
uri?.let {
try {
val httpClient = okhttp3.OkHttpClient.Builder().build()
val request = Request.Builder()
.url(it)
.delete()
.build()
httpClient.newCall(request).execute()
} catch (e: Throwable) {
Timber.tag(TAG).w(e, "Failed to delete channel")
}
}
}
}

View file

@ -18,7 +18,6 @@ package org.matrix.android.sdk.api.session.crypto
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.olm.OlmException
/**
* Represents a crypto error response.
@ -34,8 +33,6 @@ sealed class MXCryptoError : Throwable() {
val detailedErrorDescription: String? = null
) : MXCryptoError()
data class OlmError(val olmException: OlmException) : MXCryptoError()
data class UnknownDevice(val deviceList: MXUsersDevicesMap<CryptoDeviceInfo>) : MXCryptoError()
enum class ErrorType {

View file

@ -53,7 +53,6 @@ import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.coroutines.builder.safeInvokeOnClose
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.DefaultKeysAlgorithmAndData
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysAlgorithmAndData
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.crypto.verification.SasVerification
import org.matrix.android.sdk.internal.crypto.verification.VerificationRequest
@ -318,22 +317,6 @@ internal class OlmMachine @Inject constructor(
inner.receiveVerificationEvent(serializedEvent, roomId)
}
/**
* Used for lazy migration of inboundGroupSession from EA to ER.
*/
suspend fun importRoomKey(inbound: MXInboundMegolmSessionWrapper): Result<Unit> {
Timber.v("Migration:: Tentative lazy migration")
return withContext(coroutineDispatchers.io) {
val export = inbound.exportKeys()
?: return@withContext Result.failure(Exception("Failed to export key"))
val result = importDecryptedKeys(listOf(export), null).also {
Timber.v("Migration:: Tentative lazy migration result: ${it.totalNumberOfKeys}")
}
if (result.totalNumberOfKeys == 1) return@withContext Result.success(Unit)
return@withContext Result.failure(Exception("Import failed"))
}
}
/**
* Mark the given list of users to be tracked, triggering a key query request for them.
*

View file

@ -121,7 +121,8 @@ internal class PrepareToEncryptUseCase @Inject constructor(
HistoryVisibility.INVITED
} else {
HistoryVisibility.JOINED
}
},
errorOnVerifiedUserProblem = false,
)
measureTimeMillis {
keyShareLock.withLock {

View file

@ -504,15 +504,8 @@ internal class RustCryptoService @Inject constructor(
val content = event.content?.toModel<EncryptedEventContent>() ?: throw mxCryptoError
val roomId = event.roomId
val sessionId = content.sessionId
val senderKey = content.senderKey
if (roomId != null && sessionId != null) {
// try to perform a lazy migration from legacy store
val legacy = tryOrNull("Failed to access legacy crypto store") {
cryptoStore.getInboundGroupSession(sessionId, senderKey.orEmpty())
}
if (legacy == null || olmMachine.importRoomKey(legacy).isFailure) {
perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId)
}
perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId)
}
}
throw mxCryptoError
@ -851,9 +844,9 @@ internal class RustCryptoService @Inject constructor(
override fun removeSessionListener(listener: NewSessionListener) {
megolmSessionImportManager.removeListener(listener)
}
/* ==========================================================================================
* DEBUG INFO
* ========================================================================================== */
/* ==========================================================================================
* DEBUG INFO
* ========================================================================================== */
override fun toString(): String {
return "DefaultCryptoService of $myUserId ($deviceId)"

View file

@ -65,7 +65,6 @@ import org.matrix.android.sdk.internal.crypto.network.RequestSender
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import org.matrix.olm.OlmException
import org.matrix.rustcomponents.sdk.crypto.Request
import org.matrix.rustcomponents.sdk.crypto.RequestType
import org.matrix.rustcomponents.sdk.crypto.SignatureVerification
@ -840,8 +839,8 @@ internal class RustKeyBackupService @Inject constructor(
try {
olmMachine.enableBackupV1(retrievedMegolmBackupAuthData.publicKey, keysVersionResult.version)
keysBackupVersion = keysVersionResult
} catch (e: OlmException) {
Timber.e(e, "OlmException")
} catch (e: Exception) {
Timber.e(e, "Exception")
keysBackupStateManager.state = KeysBackupState.Disabled
return
}

View file

@ -1,98 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.model
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
import org.matrix.olm.OlmInboundGroupSession
import timber.log.Timber
data class MXInboundMegolmSessionWrapper(
// olm object
val session: OlmInboundGroupSession,
// data about the session
val sessionData: InboundGroupSessionData
) {
// shortcut
val roomId = sessionData.roomId
val senderKey = sessionData.senderKey
val safeSessionId = tryOrNull("Fail to get megolm session Id") { session.sessionIdentifier() }
/**
* Export the inbound group session keys.
* @param index the index to export. If null, the first known index will be used
* @return the inbound group session as MegolmSessionData if the operation succeeds
*/
internal fun exportKeys(index: Long? = null): MegolmSessionData? {
return try {
val keysClaimed = sessionData.keysClaimed ?: return null
val wantedIndex = index ?: session.firstKnownIndex
MegolmSessionData(
senderClaimedEd25519Key = sessionData.keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = sessionData.forwardingCurve25519KeyChain?.toList().orEmpty(),
sessionKey = session.export(wantedIndex),
senderClaimedKeys = keysClaimed,
roomId = sessionData.roomId,
sessionId = session.sessionIdentifier(),
senderKey = senderKey,
algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
sharedHistory = sessionData.sharedHistory
)
} catch (e: Exception) {
Timber.e(e, "## Failed to export megolm : sessionID ${tryOrNull { session.sessionIdentifier() }} failed")
null
}
}
companion object {
/**
* @exportFormat true if the megolm keys are in export format
* (ie, they lack an ed25519 signature)
*/
@Throws
internal fun newFromMegolmData(megolmSessionData: MegolmSessionData, exportFormat: Boolean): MXInboundMegolmSessionWrapper {
val exportedKey = megolmSessionData.sessionKey ?: throw IllegalArgumentException("key data not found")
val inboundSession = if (exportFormat) {
OlmInboundGroupSession.importSession(exportedKey)
} else {
OlmInboundGroupSession(exportedKey)
}
.also {
if (it.sessionIdentifier() != megolmSessionData.sessionId) {
it.releaseSession()
throw IllegalStateException("Mismatched group session Id")
}
}
val data = InboundGroupSessionData(
roomId = megolmSessionData.roomId,
senderKey = megolmSessionData.senderKey,
keysClaimed = megolmSessionData.senderClaimedKeys,
forwardingCurve25519KeyChain = megolmSessionData.forwardingCurve25519KeyChain,
sharedHistory = megolmSessionData.sharedHistory,
trusted = false
)
return MXInboundMegolmSessionWrapper(
inboundSession,
data
)
}
}
}

View file

@ -1,132 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.model
import org.matrix.android.sdk.api.util.JsonDict
import timber.log.Timber
internal data class MXKey(
/**
* The type of the key (in the example: "signed_curve25519").
*/
val type: String,
/**
* The id of the key (in the example: "AAAAFw").
*/
private val keyId: String,
/**
* The key (in the example: "IjwIcskng7YjYcn0tS8TUOT2OHHtBSfMpcfIczCgXj4").
*/
val value: String,
/**
* signature user Id to [deviceid][signature].
*/
private val signatures: Map<String, Map<String, String>>,
/**
* We have to store the original json because it can contain other fields
* that we don't support yet but they would be needed to check signatures.
*/
private val rawMap: JsonDict
) {
/**
* @return the signed data map
*/
fun signalableJSONDictionary(): Map<String, Any> {
return rawMap.filter {
it.key != "signatures" && it.key != "unsigned"
}
}
/**
* Returns a signature for an user Id and a signkey.
*
* @param userId the user id
* @param signkey the sign key
* @return the signature
*/
fun signatureForUserId(userId: String, signkey: String): String? {
// sanity checks
if (userId.isNotBlank() && signkey.isNotBlank()) {
return signatures[userId]?.get(signkey)
}
return null
}
companion object {
/**
* Key types.
*/
const val KEY_CURVE_25519_TYPE = "curve25519"
const val KEY_SIGNED_CURVE_25519_TYPE = "signed_curve25519"
// const val KEY_ED_25519_TYPE = "ed25519"
/**
* Convert a map to a MXKey.
*
* @param map the map to convert
*
* Json Example:
*
* <pre>
* "signed_curve25519:AAAAFw": {
* "key": "IjwIcskng7YjYcn0tS8TUOT2OHHtBSfMpcfIczCgXj4",
* "fallback" : true|false
* "signatures": {
* "@userId:matrix.org": {
* "ed25519:GMJRREOASV": "EUjp6pXzK9u3SDFR\/qLbzpOi3bEREeI6qMnKzXu992HsfuDDZftfJfiUXv9b\/Hqq1og4qM\/vCQJGTHAWMmgkCg"
* }
* }
* }
* </pre>
*
* into several val members
*/
fun from(map: Map<String, JsonDict>?): MXKey? {
if (map?.isNotEmpty() == true) {
val firstKey = map.keys.first()
val components = firstKey.split(":").dropLastWhile { it.isEmpty() }
if (components.size == 2) {
val params = map[firstKey]
if (params != null) {
if (params["key"] is String) {
@Suppress("UNCHECKED_CAST")
return MXKey(
type = components[0],
keyId = components[1],
value = params["key"] as String,
signatures = params["signatures"] as Map<String, Map<String, String>>,
rawMap = params
)
}
}
}
}
// Error case
Timber.e("## Unable to parse map")
return null
}
}
}

View file

@ -1,32 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.model
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import java.io.Serializable
internal data class MXOlmSessionResult(
/**
* the device.
*/
val deviceInfo: CryptoDeviceInfo,
/**
* Base64 olm session id.
* null if no session could be established.
*/
var sessionId: String?
) : Serializable

View file

@ -1,27 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.model
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
internal fun <T> MXUsersDevicesMap<T>.toDebugString() =
map.entries.joinToString { "${it.key} [${it.value.keys.joinToString { it }}]" }
internal fun <T> MXUsersDevicesMap<T>.toDebugCount() =
map.entries.fold(0) { acc, new ->
acc + new.value.keys.size
}

View file

@ -1,151 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.model
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
import org.matrix.olm.OlmInboundGroupSession
import timber.log.Timber
import java.io.Serializable
/**
* This class adds more context to a OlmInboundGroupSession object.
* This allows additional checks. The class implements Serializable so that the context can be stored.
*/
internal class OlmInboundGroupSessionWrapper : Serializable {
// The associated olm inbound group session.
var olmInboundGroupSession: OlmInboundGroupSession? = null
// The room in which this session is used.
var roomId: String? = null
// The base64-encoded curve25519 key of the sender.
var senderKey: String? = null
// Other keys the sender claims.
var keysClaimed: Map<String, String>? = null
// Devices which forwarded this session to us (normally empty).
var forwardingCurve25519KeyChain: List<String>? = ArrayList()
/**
* @return the first known message index
*/
val firstKnownIndex: Long?
get() {
if (null != olmInboundGroupSession) {
try {
return olmInboundGroupSession!!.firstKnownIndex
} catch (e: Exception) {
Timber.e(e, "## getFirstKnownIndex() : getFirstKnownIndex failed")
}
}
return null
}
/**
* Constructor.
*
* @param sessionKey the session key
* @param isImported true if it is an imported session key
*/
constructor(sessionKey: String, isImported: Boolean) {
try {
if (!isImported) {
olmInboundGroupSession = OlmInboundGroupSession(sessionKey)
} else {
olmInboundGroupSession = OlmInboundGroupSession.importSession(sessionKey)
}
} catch (e: Exception) {
Timber.e(e, "Cannot create")
}
}
/**
* Create a new instance from the provided keys map.
*
* @param megolmSessionData the megolm session data
* @throws Exception if the data are invalid
*/
@Throws(Exception::class)
constructor(megolmSessionData: MegolmSessionData) {
try {
olmInboundGroupSession = OlmInboundGroupSession.importSession(megolmSessionData.sessionKey!!)
if (olmInboundGroupSession!!.sessionIdentifier() != megolmSessionData.sessionId) {
throw Exception("Mismatched group session Id")
}
senderKey = megolmSessionData.senderKey
keysClaimed = megolmSessionData.senderClaimedKeys
roomId = megolmSessionData.roomId
} catch (e: Exception) {
throw Exception(e.message)
}
}
/**
* Export the inbound group session keys.
*
* @return the inbound group session as MegolmSessionData if the operation succeeds
*/
fun exportKeys(): MegolmSessionData? {
return try {
if (null == forwardingCurve25519KeyChain) {
forwardingCurve25519KeyChain = ArrayList()
}
if (keysClaimed == null) {
return null
}
MegolmSessionData(
senderClaimedEd25519Key = keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = ArrayList(forwardingCurve25519KeyChain!!),
senderKey = senderKey,
senderClaimedKeys = keysClaimed,
roomId = roomId,
sessionId = olmInboundGroupSession!!.sessionIdentifier(),
sessionKey = olmInboundGroupSession!!.export(olmInboundGroupSession!!.firstKnownIndex),
algorithm = MXCRYPTO_ALGORITHM_MEGOLM
)
} catch (e: Exception) {
Timber.e(e, "## export() : senderKey $senderKey failed")
null
}
}
/**
* Export the session for a message index.
*
* @param messageIndex the message index
* @return the exported data
*/
fun exportSession(messageIndex: Long): String? {
if (null != olmInboundGroupSession) {
try {
return olmInboundGroupSession!!.export(messageIndex)
} catch (e: Exception) {
Timber.e(e, "## exportSession() : export failed")
}
}
return null
}
}

View file

@ -1,158 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.model
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
import org.matrix.olm.OlmInboundGroupSession
import timber.log.Timber
import java.io.Serializable
/**
* This class adds more context to a OlmInboundGroupSession object.
* This allows additional checks. The class implements Serializable so that the context can be stored.
*/
// Note used anymore, just for database migration
// Deprecated("Use MXInboundMegolmSessionWrapper")
internal class OlmInboundGroupSessionWrapper2 : Serializable {
// The associated olm inbound group session.
var olmInboundGroupSession: OlmInboundGroupSession? = null
// The room in which this session is used.
var roomId: String? = null
// The base64-encoded curve25519 key of the sender.
var senderKey: String? = null
// Other keys the sender claims.
var keysClaimed: Map<String, String>? = null
// Devices which forwarded this session to us (normally empty).
var forwardingCurve25519KeyChain: List<String>? = ArrayList()
/**
* @return the first known message index
*/
val firstKnownIndex: Long?
get() {
return try {
olmInboundGroupSession?.firstKnownIndex
} catch (e: Exception) {
Timber.e(e, "## getFirstKnownIndex() : getFirstKnownIndex failed")
null
}
}
/**
* Constructor.
*
* @param sessionKey the session key
* @param isImported true if it is an imported session key
*/
constructor(sessionKey: String, isImported: Boolean) {
try {
if (!isImported) {
olmInboundGroupSession = OlmInboundGroupSession(sessionKey)
} else {
olmInboundGroupSession = OlmInboundGroupSession.importSession(sessionKey)
}
} catch (e: Exception) {
Timber.e(e, "Cannot create")
}
}
constructor() {
// empty
}
/**
* Create a new instance from the provided keys map.
*
* @param megolmSessionData the megolm session data
* @throws Exception if the data are invalid
*/
@Throws(Exception::class)
constructor(megolmSessionData: MegolmSessionData) {
try {
val safeSessionKey = megolmSessionData.sessionKey ?: throw Exception("invalid data")
olmInboundGroupSession = OlmInboundGroupSession.importSession(safeSessionKey)
.also {
if (it.sessionIdentifier() != megolmSessionData.sessionId) {
throw Exception("Mismatched group session Id")
}
}
senderKey = megolmSessionData.senderKey
keysClaimed = megolmSessionData.senderClaimedKeys
roomId = megolmSessionData.roomId
} catch (e: Exception) {
throw Exception(e.message)
}
}
/**
* Export the inbound group session keys.
* @param index the index to export. If null, the first known index will be used
*
* @return the inbound group session as MegolmSessionData if the operation succeeds
*/
fun exportKeys(index: Long? = null): MegolmSessionData? {
return try {
if (null == forwardingCurve25519KeyChain) {
forwardingCurve25519KeyChain = ArrayList()
}
if (keysClaimed == null) {
return null
}
val safeOlmInboundGroupSession = olmInboundGroupSession ?: return null
val wantedIndex = index ?: safeOlmInboundGroupSession.firstKnownIndex
MegolmSessionData(
senderClaimedEd25519Key = keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = forwardingCurve25519KeyChain?.toList().orEmpty(),
senderKey = senderKey,
senderClaimedKeys = keysClaimed,
roomId = roomId,
sessionId = safeOlmInboundGroupSession.sessionIdentifier(),
sessionKey = safeOlmInboundGroupSession.export(wantedIndex),
algorithm = MXCRYPTO_ALGORITHM_MEGOLM
)
} catch (e: Exception) {
Timber.e(e, "## export() : senderKey $senderKey failed")
null
}
}
/**
* Export the session for a message index.
*
* @param messageIndex the message index
* @return the exported data
*/
fun exportSession(messageIndex: Long): String? {
return try {
return olmInboundGroupSession?.export(messageIndex)
} catch (e: Exception) {
Timber.e(e, "## exportSession() : export failed")
null
}
}
}

View file

@ -1,40 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.model
import kotlinx.coroutines.sync.Mutex
import org.matrix.olm.OlmSession
/**
* Encapsulate a OlmSession and a last received message Timestamp.
*/
internal data class OlmSessionWrapper(
// The associated olm session.
val olmSession: OlmSession,
// Timestamp at which the session last received a message.
var lastReceivedMessageTs: Long = 0,
val mutex: Mutex = Mutex()
) {
/**
* Notify that a message has been received on this olm session so that it updates `lastReceivedMessageTs`.
*/
fun onMessageReceived(currentTimeMillis: Long) {
lastReceivedMessageTs = currentTimeMillis
}
}

View file

@ -1,28 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.model
import org.matrix.olm.OlmOutboundGroupSession
internal data class OutboundGroupSessionWrapper(
val outboundGroupSession: OlmOutboundGroupSession,
val creationTime: Long,
/**
* As per MSC 3061, declares if this key could be shared when inviting a new user to the room.
*/
val sharedHistory: Boolean = false
)

View file

@ -20,7 +20,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2
import org.matrix.android.sdk.api.crypto.SSSS_ALGORITHM_CURVE25519_AES_SHA2
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
@ -44,9 +43,7 @@ import org.matrix.android.sdk.api.util.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.keysbackup.generatePrivateKeyWithPassword
import org.matrix.android.sdk.internal.crypto.tools.HkdfSha256
import org.matrix.android.sdk.internal.crypto.tools.withOlmDecryption
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.olm.OlmPkMessage
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.Mac
@ -321,22 +318,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
?: throw SharedSecretStorageError.ParsingError
val algorithm = key.keyInfo.content
if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) {
val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat
return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
// decrypt from recovery key
withOlmDecryption { olmPkDecryption ->
olmPkDecryption.setPrivateKey(keySpec.privateKey)
olmPkDecryption.decrypt(OlmPkMessage()
.apply {
mCipherText = secretContent.ciphertext
mEphemeralKey = secretContent.ephemeral
mMac = secretContent.mac
}
)
}
}
} else if (SSSS_ALGORITHM_AES_HMAC_SHA2 == algorithm.algorithm) {
if (SSSS_ALGORITHM_AES_HMAC_SHA2 == algorithm.algorithm) {
val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat
return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
decryptAesHmacSha2(keySpec, name, secretContent)
@ -366,8 +348,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
val keyInfo = (keyInfoResult as? KeyInfoResult.Success)?.keyInfo
?: return IntegrityResult.Error(SharedSecretStorageError.UnknownKey(keyId ?: ""))
if (keyInfo.content.algorithm != SSSS_ALGORITHM_AES_HMAC_SHA2 &&
keyInfo.content.algorithm != SSSS_ALGORITHM_CURVE25519_AES_SHA2) {
if (keyInfo.content.algorithm != SSSS_ALGORITHM_AES_HMAC_SHA2) {
// Unsupported algorithm
return IntegrityResult.Error(
SharedSecretStorageError.UnsupportedAlgorithm(keyInfo.content.algorithm ?: "")

View file

@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.session.crypto.model.CryptoRoomInfo
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
/**
@ -143,14 +142,4 @@ interface IMXCommonCryptoStore {
* @return the device or null if not found
*/
fun deviceWithIdentityKey(userId: String, identityKey: String): CryptoDeviceInfo?
/**
* Retrieve an inbound group session.
* Used in rust for lazy migration
*
* @param sessionId the session identifier.
* @param senderKey the base64-encoded curve25519 key of the sender.
* @return an inbound group session.
*/
fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper?
}

View file

@ -35,32 +35,23 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventCo
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.crypto.OlmMachine
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransaction
import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransactionAsync
import org.matrix.android.sdk.internal.crypto.store.db.doWithRealm
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CryptoRoomInfoMapper
import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.createPrimaryKey
import org.matrix.android.sdk.internal.crypto.store.db.query.getById
import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate
import org.matrix.android.sdk.internal.di.CryptoDatabase
import org.matrix.android.sdk.internal.di.DeviceId
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.util.time.Clock
import timber.log.Timber
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
@ -75,7 +66,6 @@ private val loggerTag = LoggerTag("RealmCryptoStore", LoggerTag.CRYPTO)
@SessionScope
internal class RustCryptoStore @Inject constructor(
@CryptoDatabase private val realmConfiguration: RealmConfiguration,
private val clock: Clock,
@UserId private val userId: String,
@DeviceId private val deviceId: String,
private val myDeviceLastSeenInfoEntityMapper: MyDeviceLastSeenInfoEntityMapper,
@ -134,20 +124,6 @@ internal class RustCryptoStore @Inject constructor(
}
}
/**
* Needed for lazy migration of sessions from the legacy store.
*/
override fun getInboundGroupSession(sessionId: String, senderKey: String): MXInboundMegolmSessionWrapper? {
val key = OlmInboundGroupSessionEntity.createPrimaryKey(sessionId, senderKey)
return doWithRealm(realmConfiguration) { realm ->
realm.where<OlmInboundGroupSessionEntity>()
.equalTo(OlmInboundGroupSessionEntityFields.PRIMARY_KEY, key)
.findFirst()
?.toModel()
}
}
// ================================================
// Things that should be migrated to another store than realm
// ================================================
@ -163,30 +139,7 @@ internal class RustCryptoStore @Inject constructor(
// nop
}
override fun tidyUpDataBase() {
// These entities are not used in rust actually, but as they are not yet cleaned up, this will do it with time
val prevWeekTs = clock.epochMillis() - 7 * 24 * 60 * 60 * 1_000
doRealmTransaction("tidyUpDataBase", realmConfiguration) { realm ->
// Clean the old ones?
realm.where<OutgoingKeyRequestEntity>()
.lessThan(OutgoingKeyRequestEntityFields.CREATION_TIME_STAMP, prevWeekTs)
.findAll()
.also { Timber.i("## Crypto Clean up ${it.size} OutgoingKeyRequestEntity") }
.deleteAllFromRealm()
// Only keep one month history
val prevMonthTs = clock.epochMillis() - 4 * 7 * 24 * 60 * 60 * 1_000L
realm.where<AuditTrailEntity>()
.lessThan(AuditTrailEntityFields.AGE_LOCAL_TS, prevMonthTs)
.findAll()
.also { Timber.i("## Crypto Clean up ${it.size} AuditTrailEntity") }
.deleteAllFromRealm()
// Can we do something for WithHeldSessionEntity?
}
}
override fun tidyUpDataBase() = Unit
override fun close() {
val tasks = monarchyWriteAsyncExecutor.shutdownNow()

View file

@ -1,31 +0,0 @@
/*
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
*
* 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.store
import org.matrix.android.sdk.api.session.crypto.crosssigning.UserIdentity
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
internal data class UserDataToStore(
/**
* Map of userId -> (Map of deviceId -> [CryptoDeviceInfo]).
*/
val userDevices: MutableMap<String, Map<String, CryptoDeviceInfo>> = mutableMapOf(),
/**
* Map of userId -> [UserIdentity].
*/
val userIdentities: MutableMap<String, UserIdentity> = mutableMapOf(),
)

View file

@ -16,15 +16,9 @@
package org.matrix.android.sdk.internal.crypto.store.db
import android.util.Base64
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.RealmObject
import timber.log.Timber
import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import kotlin.system.measureTimeMillis
/**
@ -36,24 +30,6 @@ internal fun <T> doWithRealm(realmConfiguration: RealmConfiguration, action: (Re
}
}
/**
* Get realm, do the query, copy from realm, close realm, and return the copied result.
*/
internal fun <T : RealmObject> doRealmQueryAndCopy(realmConfiguration: RealmConfiguration, action: (Realm) -> T?): T? {
return Realm.getInstance(realmConfiguration).use { realm ->
action.invoke(realm)?.let { realm.copyFromRealm(it) }
}
}
/**
* Get realm, do the list query, copy from realm, close realm, and return the copied result.
*/
internal fun <T : RealmObject> doRealmQueryAndCopyList(realmConfiguration: RealmConfiguration, action: (Realm) -> Iterable<T>): Iterable<T> {
return Realm.getInstance(realmConfiguration).use { realm ->
action.invoke(realm).let { realm.copyFromRealm(it) }
}
}
/**
* Get realm instance, invoke the action in a transaction and close realm.
*/
@ -70,38 +46,3 @@ internal fun doRealmTransactionAsync(realmConfiguration: RealmConfiguration, act
realm.executeTransactionAsync { action.invoke(it) }
}
}
/**
* Serialize any Serializable object, zip it and convert to Base64 String.
*/
internal fun serializeForRealm(o: Any?): String? {
if (o == null) {
return null
}
val baos = ByteArrayOutputStream()
val gzis = GZIPOutputStream(baos)
val out = ObjectOutputStream(gzis)
out.use {
it.writeObject(o)
}
return Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT)
}
/**
* Do the opposite of serializeForRealm.
*/
@Suppress("UNCHECKED_CAST")
internal fun <T> deserializeFromRealm(string: String?): T? {
if (string == null) {
return null
}
val decodedB64 = Base64.decode(string.toByteArray(), Base64.DEFAULT)
val bais = decodedB64.inputStream()
val gzis = GZIPInputStream(bais)
val ois = SafeObjectInputStream(gzis)
return ois.use {
it.readObject() as T
}
}

View file

@ -17,31 +17,8 @@
package org.matrix.android.sdk.internal.crypto.store.db
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo001Legacy
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo002Legacy
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo003RiotX
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo004
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo005
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo006
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo007
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo008
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo009
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo010
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo011
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo012
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo013
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo014
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo015
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo016
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo017
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo018
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo019
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo020
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo021
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo022
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo023
import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo024
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import org.matrix.android.sdk.internal.util.time.Clock
import javax.inject.Inject
/**
@ -49,13 +26,11 @@ import javax.inject.Inject
* 0, 1, 2: legacy Riot-Android;
* 3: migrate to RiotX schema;
* 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6).
* 24: Delete nearly all the crypto DB
*/
internal class RealmCryptoStoreMigration @Inject constructor(
private val clock: Clock,
private val rustMigrationInfoProvider: RustMigrationInfoProvider,
) : MatrixRealmMigration(
internal class RealmCryptoStoreMigration @Inject constructor() : MatrixRealmMigration(
dbName = "Crypto",
schemaVersion = 23L,
schemaVersion = 24L,
) {
/**
* Forces all RealmCryptoStoreMigration instances to be equal.
@ -65,33 +40,6 @@ internal class RealmCryptoStoreMigration @Inject constructor(
override fun hashCode() = 5000
override fun doMigrate(realm: DynamicRealm, oldVersion: Long) {
if (oldVersion < 1) MigrateCryptoTo001Legacy(realm).perform()
if (oldVersion < 2) MigrateCryptoTo002Legacy(realm).perform()
if (oldVersion < 3) MigrateCryptoTo003RiotX(realm).perform()
if (oldVersion < 4) MigrateCryptoTo004(realm).perform()
if (oldVersion < 5) MigrateCryptoTo005(realm).perform()
if (oldVersion < 6) MigrateCryptoTo006(realm).perform()
if (oldVersion < 7) MigrateCryptoTo007(realm).perform()
if (oldVersion < 8) MigrateCryptoTo008(realm, clock).perform()
if (oldVersion < 9) MigrateCryptoTo009(realm).perform()
if (oldVersion < 10) MigrateCryptoTo010(realm).perform()
if (oldVersion < 11) MigrateCryptoTo011(realm).perform()
if (oldVersion < 12) MigrateCryptoTo012(realm).perform()
if (oldVersion < 13) MigrateCryptoTo013(realm).perform()
if (oldVersion < 14) MigrateCryptoTo014(realm).perform()
if (oldVersion < 15) MigrateCryptoTo015(realm).perform()
if (oldVersion < 16) MigrateCryptoTo016(realm).perform()
if (oldVersion < 17) MigrateCryptoTo017(realm).perform()
if (oldVersion < 18) MigrateCryptoTo018(realm).perform()
if (oldVersion < 19) MigrateCryptoTo019(realm).perform()
if (oldVersion < 20) MigrateCryptoTo020(realm).perform()
if (oldVersion < 21) MigrateCryptoTo021(realm).perform()
if (oldVersion < 22) MigrateCryptoTo022(
realm,
rustMigrationInfoProvider.rustDirectory,
rustMigrationInfoProvider.rustEncryptionConfiguration,
rustMigrationInfoProvider.migrateMegolmGroupSessions
).perform()
if (oldVersion < 23) MigrateCryptoTo023(realm).perform()
if (oldVersion < 24) MigrateCryptoTo024(realm).perform()
}
}

View file

@ -17,23 +17,9 @@
package org.matrix.android.sdk.internal.crypto.store.db
import io.realm.annotations.RealmModule
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyRequestReplyEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntity
/**
* Realm module for Crypto store classes.
@ -43,21 +29,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEnti
classes = [
CryptoMetadataEntity::class,
CryptoRoomEntity::class,
DeviceInfoEntity::class,
KeysBackupDataEntity::class,
OlmInboundGroupSessionEntity::class,
OlmSessionEntity::class,
UserEntity::class,
KeyInfoEntity::class,
CrossSigningInfoEntity::class,
TrustLevelEntity::class,
AuditTrailEntity::class,
OutgoingKeyRequestEntity::class,
KeyRequestReplyEntity::class,
MyDeviceLastSeenInfoEntity::class,
WithHeldSessionEntity::class,
SharedSessionEntity::class,
OutboundGroupSessionInfoEntity::class
]
)
internal class RealmCryptoStoreModule

View file

@ -1,31 +0,0 @@
/*
* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* 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.store.db
import org.matrix.android.sdk.internal.crypto.RustEncryptionConfiguration
import org.matrix.android.sdk.internal.di.SessionRustFilesDirectory
import java.io.File
import javax.inject.Inject
internal class RustMigrationInfoProvider @Inject constructor(
@SessionRustFilesDirectory
val rustDirectory: File,
val rustEncryptionConfiguration: RustEncryptionConfiguration
) {
var migrateMegolmGroupSessions: Boolean = false
}

View file

@ -1,44 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.store.db
import java.io.IOException
import java.io.InputStream
import java.io.ObjectInputStream
import java.io.ObjectStreamClass
/**
* Package has been renamed from `im.vector.matrix.android` to `org.matrix.android.sdk`
* so ensure deserialization of previously stored objects still works
*
* Ref: https://stackoverflow.com/questions/3884492/how-can-i-change-package-for-a-bunch-of-java-serializable-classes
*/
internal class SafeObjectInputStream(inputStream: InputStream) : ObjectInputStream(inputStream) {
init {
enableResolveObject(true)
}
@Throws(IOException::class, ClassNotFoundException::class)
override fun readClassDescriptor(): ObjectStreamClass {
val read = super.readClassDescriptor()
if (read.name.startsWith("im.vector.matrix.android.")) {
return ObjectStreamClass.lookup(Class.forName(read.name.replace("im.vector.matrix.android.", "org.matrix.android.sdk.")))
}
return read
}
}

View file

@ -1,87 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.store.db.mapper
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import io.realm.RealmList
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntity
import timber.log.Timber
import javax.inject.Inject
internal class CrossSigningKeysMapper @Inject constructor(moshi: Moshi) {
private val signaturesAdapter = moshi.adapter<Map<String, Map<String, String>>>(
Types.newParameterizedType(
Map::class.java,
String::class.java,
Any::class.java
)
)
fun update(keyInfo: KeyInfoEntity, cryptoCrossSigningKey: CryptoCrossSigningKey) {
// update signatures?
keyInfo.signatures = serializeSignatures(cryptoCrossSigningKey.signatures)
keyInfo.usages = cryptoCrossSigningKey.usages?.toTypedArray()?.let { RealmList(*it) }
?: RealmList()
}
fun map(userId: String?, keyInfo: KeyInfoEntity?): CryptoCrossSigningKey? {
val pubKey = keyInfo?.publicKeyBase64 ?: return null
return CryptoCrossSigningKey(
userId = userId ?: "",
keys = mapOf("ed25519:$pubKey" to pubKey),
usages = keyInfo.usages.toList(),
signatures = deserializeSignatures(keyInfo.signatures),
trustLevel = keyInfo.trustLevelEntity?.let {
DeviceTrustLevel(
crossSigningVerified = it.crossSignedVerified ?: false,
locallyVerified = it.locallyVerified ?: false
)
}
)
}
fun map(keyInfo: CryptoCrossSigningKey): KeyInfoEntity {
return KeyInfoEntity().apply {
publicKeyBase64 = keyInfo.unpaddedBase64PublicKey
usages = keyInfo.usages?.let { RealmList(*it.toTypedArray()) } ?: RealmList()
signatures = serializeSignatures(keyInfo.signatures)
// TODO how to handle better, check if same keys?
// reset trust
trustLevelEntity = null
}
}
fun serializeSignatures(signatures: Map<String, Map<String, String>>?): String {
return signaturesAdapter.toJson(signatures)
}
fun deserializeSignatures(signatures: String?): Map<String, Map<String, String>>? {
if (signatures == null) {
return null
}
return try {
signaturesAdapter.fromJson(signatures)
} catch (failure: Throwable) {
Timber.e(failure)
null
}
}
}

View file

@ -1,35 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
internal class MigrateCryptoTo001Legacy(realm: DynamicRealm) : RealmMigrator(realm, 1) {
override fun doMigrate(realm: DynamicRealm) {
Timber.d("Add field lastReceivedMessageTs (Long) and set the value to 0")
realm.schema.get("OlmSessionEntity")
?.addField(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, Long::class.java)
?.transform {
it.setLong(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, 0)
}
}
}

View file

@ -1,86 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
internal class MigrateCryptoTo002Legacy(realm: DynamicRealm) : RealmMigrator(realm, 2) {
override fun doMigrate(realm: DynamicRealm) {
Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields")
realm.schema.get("IncomingRoomKeyRequestEntity")
?.addFieldIfNotExists("requestBodyAlgorithm", String::class.java)
?.addFieldIfNotExists("requestBodyRoomId", String::class.java)
?.addFieldIfNotExists("requestBodySenderKey", String::class.java)
?.addFieldIfNotExists("requestBodySessionId", String::class.java)
?.transform { dynamicObject ->
try {
val requestBodyString = dynamicObject.getString("requestBodyString")
// It was a map before
val map: Map<String, String>? = deserializeFromRealm(requestBodyString)
map?.let {
dynamicObject.setString("requestBodyAlgorithm", it["algorithm"])
dynamicObject.setString("requestBodyRoomId", it["room_id"])
dynamicObject.setString("requestBodySenderKey", it["sender_key"])
dynamicObject.setString("requestBodySessionId", it["session_id"])
}
} catch (e: Exception) {
Timber.e(e, "Error")
}
}
?.removeFieldIfExists("requestBodyString")
Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields")
realm.schema.get("OutgoingRoomKeyRequestEntity")
?.addFieldIfNotExists("requestBodyAlgorithm", String::class.java)
?.addFieldIfNotExists("requestBodyRoomId", String::class.java)
?.addFieldIfNotExists("requestBodySenderKey", String::class.java)
?.addFieldIfNotExists("requestBodySessionId", String::class.java)
?.transform { dynamicObject ->
try {
val requestBodyString = dynamicObject.getString("requestBodyString")
// It was a map before
val map: Map<String, String>? = deserializeFromRealm(requestBodyString)
map?.let {
dynamicObject.setString("requestBodyAlgorithm", it["algorithm"])
dynamicObject.setString("requestBodyRoomId", it["room_id"])
dynamicObject.setString("requestBodySenderKey", it["sender_key"])
dynamicObject.setString("requestBodySessionId", it["session_id"])
}
} catch (e: Exception) {
Timber.e(e, "Error")
}
}
?.removeFieldIfExists("requestBodyString")
Timber.d("Create KeysBackupDataEntity")
if (!realm.schema.contains("KeysBackupDataEntity")) {
realm.schema.create("KeysBackupDataEntity")
.addField(KeysBackupDataEntityFields.PRIMARY_KEY, Integer::class.java)
.addPrimaryKey(KeysBackupDataEntityFields.PRIMARY_KEY)
.setRequired(KeysBackupDataEntityFields.PRIMARY_KEY, true)
.addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_HASH, String::class.java)
.addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_NUMBER_OF_KEYS, Integer::class.java)
}
}
}

View file

@ -1,83 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import org.matrix.androidsdk.crypto.data.MXDeviceInfo
import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2
import timber.log.Timber
internal class MigrateCryptoTo003RiotX(realm: DynamicRealm) : RealmMigrator(realm, 3) {
override fun doMigrate(realm: DynamicRealm) {
Timber.d("Migrate to RiotX model")
realm.schema.get("CryptoRoomEntity")
?.addFieldIfNotExists(CryptoRoomEntityFields.SHOULD_ENCRYPT_FOR_INVITED_MEMBERS, Boolean::class.java)
?.setRequiredIfNotAlready(CryptoRoomEntityFields.SHOULD_ENCRYPT_FOR_INVITED_MEMBERS, false)
// Convert format of MXDeviceInfo, package has to be the same.
realm.schema.get("DeviceInfoEntity")
?.transform { obj ->
try {
val oldSerializedData = obj.getString("deviceInfoData")
deserializeFromRealm<MXDeviceInfo>(oldSerializedData)?.let { legacyMxDeviceInfo ->
val newMxDeviceInfo = org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo(
deviceId = legacyMxDeviceInfo.deviceId,
userId = legacyMxDeviceInfo.userId,
algorithms = legacyMxDeviceInfo.algorithms,
keys = legacyMxDeviceInfo.keys,
signatures = legacyMxDeviceInfo.signatures,
unsigned = legacyMxDeviceInfo.unsigned,
verified = legacyMxDeviceInfo.mVerified
)
obj.setString("deviceInfoData", serializeForRealm(newMxDeviceInfo))
}
} catch (e: Exception) {
Timber.e(e, "Error")
}
}
// Convert MXOlmInboundGroupSession2 to OlmInboundGroupSessionWrapper
realm.schema.get("OlmInboundGroupSessionEntity")
?.transform { obj ->
try {
val oldSerializedData = obj.getString("olmInboundGroupSessionData")
deserializeFromRealm<MXOlmInboundGroupSession2>(oldSerializedData)?.let { mxOlmInboundGroupSession2 ->
val sessionKey = mxOlmInboundGroupSession2.mSession.sessionIdentifier()
val newOlmInboundGroupSessionWrapper = OlmInboundGroupSessionWrapper(sessionKey, false)
.apply {
olmInboundGroupSession = mxOlmInboundGroupSession2.mSession
roomId = mxOlmInboundGroupSession2.mRoomId
senderKey = mxOlmInboundGroupSession2.mSenderKey
keysClaimed = mxOlmInboundGroupSession2.mKeysClaimed
forwardingCurve25519KeyChain = mxOlmInboundGroupSession2.mForwardingCurve25519KeyChain
}
obj.setString("olmInboundGroupSessionData", serializeForRealm(newOlmInboundGroupSessionWrapper))
}
} catch (e: Exception) {
Timber.e(e, "Error")
}
}
}
}

View file

@ -1,143 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import io.realm.DynamicRealm
import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
import org.matrix.android.sdk.internal.di.SerializeNulls
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
// Version 4L added Cross Signing info persistence
internal class MigrateCryptoTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) {
override fun doMigrate(realm: DynamicRealm) {
if (realm.schema.contains("TrustLevelEntity")) {
Timber.d("Skipping Step 3 -> 4 because entities already exist")
return
}
Timber.d("Create KeyInfoEntity")
val trustLevelEntityEntitySchema = realm.schema.create("TrustLevelEntity")
.addField(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, Boolean::class.java)
.setNullable(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, true)
.addField(TrustLevelEntityFields.LOCALLY_VERIFIED, Boolean::class.java)
.setNullable(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
val keyInfoEntitySchema = realm.schema.create("KeyInfoEntity")
.addField(KeyInfoEntityFields.PUBLIC_KEY_BASE64, String::class.java)
.addField(KeyInfoEntityFields.SIGNATURES, String::class.java)
.addRealmListField(KeyInfoEntityFields.USAGES.`$`, String::class.java)
.addRealmObjectField(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema)
Timber.d("Create CrossSigningInfoEntity")
val crossSigningInfoSchema = realm.schema.create("CrossSigningInfoEntity")
.addField(CrossSigningInfoEntityFields.USER_ID, String::class.java)
.addPrimaryKey(CrossSigningInfoEntityFields.USER_ID)
.addRealmListField(CrossSigningInfoEntityFields.CROSS_SIGNING_KEYS.`$`, keyInfoEntitySchema)
Timber.d("Updating UserEntity table")
realm.schema.get("UserEntity")
?.addRealmObjectField(UserEntityFields.CROSS_SIGNING_INFO_ENTITY.`$`, crossSigningInfoSchema)
Timber.d("Updating CryptoMetadataEntity table")
realm.schema.get("CryptoMetadataEntity")
?.addField(CryptoMetadataEntityFields.X_SIGN_MASTER_PRIVATE_KEY, String::class.java)
?.addField(CryptoMetadataEntityFields.X_SIGN_USER_PRIVATE_KEY, String::class.java)
?.addField(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY, String::class.java)
val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
val listMigrationAdapter = moshi.adapter<List<String>>(
Types.newParameterizedType(
List::class.java,
String::class.java,
Any::class.java
)
)
val mapMigrationAdapter = moshi.adapter<JsonDict>(
Types.newParameterizedType(
Map::class.java,
String::class.java,
Any::class.java
)
)
realm.schema.get("DeviceInfoEntity")
?.addField(DeviceInfoEntityFields.USER_ID, String::class.java)
?.addField(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, String::class.java)
?.addField(DeviceInfoEntityFields.KEYS_MAP_JSON, String::class.java)
?.addField(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, String::class.java)
?.addField(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, String::class.java)
?.addField(DeviceInfoEntityFields.IS_BLOCKED, Boolean::class.java)
?.setNullable(DeviceInfoEntityFields.IS_BLOCKED, true)
?.addRealmObjectField(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema)
?.transform { obj ->
try {
val oldSerializedData = obj.getString("deviceInfoData")
deserializeFromRealm<MXDeviceInfo>(oldSerializedData)?.let { oldDevice ->
val trustLevel = realm.createObject("TrustLevelEntity")
when (oldDevice.verified) {
MXDeviceInfo.DEVICE_VERIFICATION_UNKNOWN -> {
obj.setNull(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`)
}
MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED -> {
trustLevel.setNull(TrustLevelEntityFields.LOCALLY_VERIFIED)
trustLevel.setNull(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED)
obj.setBoolean(DeviceInfoEntityFields.IS_BLOCKED, oldDevice.isBlocked)
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
}
MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED -> {
trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, false)
trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
}
MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED -> {
trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
}
}
obj.setString(DeviceInfoEntityFields.USER_ID, oldDevice.userId)
obj.setString(DeviceInfoEntityFields.IDENTITY_KEY, oldDevice.identityKey())
obj.setString(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, listMigrationAdapter.toJson(oldDevice.algorithms))
obj.setString(DeviceInfoEntityFields.KEYS_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.keys))
obj.setString(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.signatures))
obj.setString(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.unsigned))
}
} catch (failure: Throwable) {
Timber.w(failure, "Crypto Data base migration error")
// an unfortunate refactor did modify that class, making deserialization failing
// so we just skip and ignore..
}
}
?.removeField("deviceInfoData")
}
}

View file

@ -1,62 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.util.database.RealmMigrator
internal class MigrateCryptoTo005(realm: DynamicRealm) : RealmMigrator(realm, 5) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.remove("OutgoingRoomKeyRequestEntity")
realm.schema.remove("IncomingRoomKeyRequestEntity")
// Not need to migrate existing request, just start fresh?
realm.schema.create("GossipingEventEntity")
.addField("type", String::class.java)
.addIndex("type")
.addField("content", String::class.java)
.addField("sender", String::class.java)
.addIndex("sender")
.addField("decryptionResultJson", String::class.java)
.addField("decryptionErrorCode", String::class.java)
.addField("ageLocalTs", Long::class.java)
.setNullable("ageLocalTs", true)
.addField("sendStateStr", String::class.java)
realm.schema.create("IncomingGossipingRequestEntity")
.addField("requestId", String::class.java)
.addIndex("requestId")
.addField("typeStr", String::class.java)
.addIndex("typeStr")
.addField("otherUserId", String::class.java)
.addField("requestedInfoStr", String::class.java)
.addField("otherDeviceId", String::class.java)
.addField("requestStateStr", String::class.java)
.addField("localCreationTimestamp", Long::class.java)
.setNullable("localCreationTimestamp", true)
realm.schema.create("OutgoingGossipingRequestEntity")
.addField("requestId", String::class.java)
.addIndex("requestId")
.addField("recipientsData", String::class.java)
.addField("requestedInfoStr", String::class.java)
.addField("typeStr", String::class.java)
.addIndex("typeStr")
.addField("requestStateStr", String::class.java)
}
}

View file

@ -1,32 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
internal class MigrateCryptoTo006(realm: DynamicRealm) : RealmMigrator(realm, 6) {
override fun doMigrate(realm: DynamicRealm) {
Timber.d("Updating CryptoMetadataEntity table")
realm.schema.get("CryptoMetadataEntity")
?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY, String::class.java)
?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY_VERSION, String::class.java)
}
}

View file

@ -1,65 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
internal class MigrateCryptoTo007(realm: DynamicRealm) : RealmMigrator(realm, 7) {
override fun doMigrate(realm: DynamicRealm) {
Timber.d("Updating KeyInfoEntity table")
val crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi())
val keyInfoEntities = realm.where("KeyInfoEntity").findAll()
try {
keyInfoEntities.forEach {
val stringSignatures = it.getString(KeyInfoEntityFields.SIGNATURES)
val objectSignatures: Map<String, Map<String, String>>? = deserializeFromRealm(stringSignatures)
val jsonSignatures = crossSigningKeysMapper.serializeSignatures(objectSignatures)
it.setString(KeyInfoEntityFields.SIGNATURES, jsonSignatures)
}
} catch (ignore: Throwable) {
}
// Migrate frozen classes
val inboundGroupSessions = realm.where("OlmInboundGroupSessionEntity").findAll()
inboundGroupSessions.forEach { dynamicObject ->
dynamicObject.getString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA)?.let { serializedObject ->
try {
deserializeFromRealm<OlmInboundGroupSessionWrapper?>(serializedObject)?.let { oldFormat ->
val newFormat = oldFormat.exportKeys()?.let {
OlmInboundGroupSessionWrapper2(it)
}
dynamicObject.setString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA, serializeForRealm(newFormat))
}
} catch (failure: Throwable) {
Timber.e(failure, "## OlmInboundGroupSessionEntity migration failed")
}
}
}
}
}

View file

@ -1,50 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import org.matrix.android.sdk.internal.util.time.Clock
internal class MigrateCryptoTo008(
realm: DynamicRealm,
private val clock: Clock,
) : RealmMigrator(realm, 8) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.create("MyDeviceLastSeenInfoEntity")
.addField(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, String::class.java)
.addPrimaryKey(MyDeviceLastSeenInfoEntityFields.DEVICE_ID)
.addField(MyDeviceLastSeenInfoEntityFields.DISPLAY_NAME, String::class.java)
.addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_IP, String::class.java)
.addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, Long::class.java)
.setNullable(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, true)
val now = clock.epochMillis()
realm.schema.get("DeviceInfoEntity")
?.addField(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, Long::class.java)
?.setNullable(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, true)
?.transform { deviceInfoEntity ->
tryOrNull {
deviceInfoEntity.setLong(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, now)
}
}
}
}

View file

@ -1,44 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
// Fixes duplicate devices in UserEntity#devices
internal class MigrateCryptoTo009(realm: DynamicRealm) : RealmMigrator(realm, 9) {
override fun doMigrate(realm: DynamicRealm) {
val userEntities = realm.where("UserEntity").findAll()
userEntities.forEach {
try {
val deviceList = it.getList(UserEntityFields.DEVICES.`$`)
?: return@forEach
val distinct = deviceList.distinctBy { it.getString(DeviceInfoEntityFields.DEVICE_ID) }
if (distinct.size != deviceList.size) {
deviceList.clear()
deviceList.addAll(distinct)
}
} catch (failure: Throwable) {
Timber.w(failure, "Crypto Data base migration error for migrateTo9")
}
}
}
}

View file

@ -1,50 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
// Version 10L added WithHeld Keys Info (MSC2399)
internal class MigrateCryptoTo010(realm: DynamicRealm) : RealmMigrator(realm, 10) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.create("WithHeldSessionEntity")
.addField(WithHeldSessionEntityFields.ROOM_ID, String::class.java)
.addField(WithHeldSessionEntityFields.ALGORITHM, String::class.java)
.addField(WithHeldSessionEntityFields.SESSION_ID, String::class.java)
.addIndex(WithHeldSessionEntityFields.SESSION_ID)
.addField(WithHeldSessionEntityFields.SENDER_KEY, String::class.java)
.addIndex(WithHeldSessionEntityFields.SENDER_KEY)
.addField(WithHeldSessionEntityFields.CODE_STRING, String::class.java)
.addField(WithHeldSessionEntityFields.REASON, String::class.java)
realm.schema.create("SharedSessionEntity")
.addField(SharedSessionEntityFields.ROOM_ID, String::class.java)
.addField(SharedSessionEntityFields.ALGORITHM, String::class.java)
.addField(SharedSessionEntityFields.SESSION_ID, String::class.java)
.addIndex(SharedSessionEntityFields.SESSION_ID)
.addField(SharedSessionEntityFields.USER_ID, String::class.java)
.addIndex(SharedSessionEntityFields.USER_ID)
.addField(SharedSessionEntityFields.DEVICE_ID, String::class.java)
.addIndex(SharedSessionEntityFields.DEVICE_ID)
.addField(SharedSessionEntityFields.CHAIN_INDEX, Long::class.java)
.setNullable(SharedSessionEntityFields.CHAIN_INDEX, true)
}
}

View file

@ -1,30 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
// Version 11L added deviceKeysSentToServer boolean to CryptoMetadataEntity
internal class MigrateCryptoTo011(realm: DynamicRealm) : RealmMigrator(realm, 11) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("CryptoMetadataEntity")
?.addField(CryptoMetadataEntityFields.DEVICE_KEYS_SENT_TO_SERVER, Boolean::class.java)
}
}

View file

@ -1,36 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
// Version 12L added outbound group session persistence
internal class MigrateCryptoTo012(realm: DynamicRealm) : RealmMigrator(realm, 12) {
override fun doMigrate(realm: DynamicRealm) {
val outboundEntitySchema = realm.schema.create("OutboundGroupSessionInfoEntity")
.addField(OutboundGroupSessionInfoEntityFields.SERIALIZED_OUTBOUND_SESSION_DATA, String::class.java)
.addField(OutboundGroupSessionInfoEntityFields.CREATION_TIME, Long::class.java)
.setNullable(OutboundGroupSessionInfoEntityFields.CREATION_TIME, true)
realm.schema.get("CryptoRoomEntity")
?.addRealmObjectField(CryptoRoomEntityFields.OUTBOUND_SESSION_INFO.`$`, outboundEntitySchema)
}
}

View file

@ -1,78 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
// Version 13L delete unreferenced TrustLevelEntity
internal class MigrateCryptoTo013(realm: DynamicRealm) : RealmMigrator(realm, 13) {
override fun doMigrate(realm: DynamicRealm) {
// Use a trick to do that... Ref: https://stackoverflow.com/questions/55221366
val trustLevelEntitySchema = realm.schema.get("TrustLevelEntity")
/*
Creating a new temp field called isLinked which is set to true for those which are
references by other objects. Rest of them are set to false. Then removing all
those which are false and hence duplicate and unnecessary. Then removing the temp field
isLinked
*/
var mainCounter = 0
var deviceInfoCounter = 0
var keyInfoCounter = 0
val deleteCounter: Int
trustLevelEntitySchema
?.addField("isLinked", Boolean::class.java)
?.transform { obj ->
// Setting to false for all by default
obj.set("isLinked", false)
mainCounter++
}
realm.schema.get("DeviceInfoEntity")?.transform { obj ->
// Setting to true for those which are referenced in DeviceInfoEntity
deviceInfoCounter++
obj.getObject("trustLevelEntity")?.set("isLinked", true)
}
realm.schema.get("KeyInfoEntity")?.transform { obj ->
// Setting to true for those which are referenced in KeyInfoEntity
keyInfoCounter++
obj.getObject("trustLevelEntity")?.set("isLinked", true)
}
// Removing all those which are set as false
realm.where("TrustLevelEntity")
.equalTo("isLinked", false)
.findAll()
.also { deleteCounter = it.size }
.deleteAllFromRealm()
trustLevelEntitySchema?.removeField("isLinked")
Timber.w("TrustLevelEntity cleanup: $mainCounter entities")
Timber.w("TrustLevelEntity cleanup: $deviceInfoCounter entities referenced in DeviceInfoEntities")
Timber.w("TrustLevelEntity cleanup: $keyInfoCounter entities referenced in KeyInfoEntity")
Timber.w("TrustLevelEntity cleanup: $deleteCounter entities deleted!")
if (mainCounter != deviceInfoCounter + keyInfoCounter + deleteCounter) {
Timber.e("TrustLevelEntity cleanup: Something is not correct...")
}
}
}

View file

@ -1,41 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
// Version 14L Update the way we remember key sharing
internal class MigrateCryptoTo014(realm: DynamicRealm) : RealmMigrator(realm, 14) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("SharedSessionEntity")
?.addField(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, String::class.java)
?.addIndex(SharedSessionEntityFields.DEVICE_IDENTITY_KEY)
?.transform {
val sharedUserId = it.getString(SharedSessionEntityFields.USER_ID)
val sharedDeviceId = it.getString(SharedSessionEntityFields.DEVICE_ID)
val knownDevice = realm.where("DeviceInfoEntity")
.equalTo(DeviceInfoEntityFields.USER_ID, sharedUserId)
.equalTo(DeviceInfoEntityFields.DEVICE_ID, sharedDeviceId)
.findFirst()
it.setString(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, knownDevice?.getString(DeviceInfoEntityFields.IDENTITY_KEY))
}
}
}

View file

@ -1,36 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
// Version 15L adds wasEncryptedOnce field to CryptoRoomEntity
internal class MigrateCryptoTo015(realm: DynamicRealm) : RealmMigrator(realm, 15) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("CryptoRoomEntity")
?.addField(CryptoRoomEntityFields.WAS_ENCRYPTED_ONCE, Boolean::class.java)
?.setNullable(CryptoRoomEntityFields.WAS_ENCRYPTED_ONCE, true)
?.transform {
val currentAlgorithm = it.getString(CryptoRoomEntityFields.ALGORITHM)
it.set(CryptoRoomEntityFields.WAS_ENCRYPTED_ONCE, currentAlgorithm == MXCRYPTO_ALGORITHM_MEGOLM)
}
}
}

View file

@ -1,70 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.AuditTrailEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyRequestReplyEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
internal class MigrateCryptoTo016(realm: DynamicRealm) : RealmMigrator(realm, 16) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.remove("OutgoingGossipingRequestEntity")
realm.schema.remove("IncomingGossipingRequestEntity")
realm.schema.remove("GossipingEventEntity")
// No need to migrate existing request, just start fresh
val replySchema = realm.schema.create("KeyRequestReplyEntity")
.addField(KeyRequestReplyEntityFields.SENDER_ID, String::class.java)
.addField(KeyRequestReplyEntityFields.FROM_DEVICE, String::class.java)
.addField(KeyRequestReplyEntityFields.EVENT_JSON, String::class.java)
realm.schema.create("OutgoingKeyRequestEntity")
.addField(OutgoingKeyRequestEntityFields.REQUEST_ID, String::class.java)
.addIndex(OutgoingKeyRequestEntityFields.REQUEST_ID)
.addField(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID, String::class.java)
.addIndex(OutgoingKeyRequestEntityFields.MEGOLM_SESSION_ID)
.addRealmListField(OutgoingKeyRequestEntityFields.REPLIES.`$`, replySchema)
.addField(OutgoingKeyRequestEntityFields.RECIPIENTS_DATA, String::class.java)
.addField(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR, String::class.java)
.addIndex(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR)
.addField(OutgoingKeyRequestEntityFields.REQUESTED_INFO_STR, String::class.java)
.addField(OutgoingKeyRequestEntityFields.ROOM_ID, String::class.java)
.addIndex(OutgoingKeyRequestEntityFields.ROOM_ID)
.addField(OutgoingKeyRequestEntityFields.REQUESTED_INDEX, Integer::class.java)
.addField(OutgoingKeyRequestEntityFields.CREATION_TIME_STAMP, Long::class.java)
.setNullable(OutgoingKeyRequestEntityFields.CREATION_TIME_STAMP, true)
realm.schema.create("AuditTrailEntity")
.addField(AuditTrailEntityFields.AGE_LOCAL_TS, Long::class.java)
.setNullable(AuditTrailEntityFields.AGE_LOCAL_TS, true)
.addField(AuditTrailEntityFields.CONTENT_JSON, String::class.java)
.addField(AuditTrailEntityFields.TYPE, String::class.java)
.addIndex(AuditTrailEntityFields.TYPE)
realm.schema.get("CryptoMetadataEntity")
?.addField(CryptoMetadataEntityFields.GLOBAL_ENABLE_KEY_GOSSIPING, Boolean::class.java)
?.transform {
// set the default value to true
it.setBoolean(CryptoMetadataEntityFields.GLOBAL_ENABLE_KEY_GOSSIPING, true)
}
}
}

View file

@ -1,101 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
/**
* Version 17L enhance OlmInboundGroupSessionEntity to support shared history for MSC3061.
* Also migrates how megolm session are stored to avoid additional serialized frozen class.
*/
internal class MigrateCryptoTo017(realm: DynamicRealm) : RealmMigrator(realm, 17) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("CryptoRoomEntity")
?.addField(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java)?.transform {
// We don't have access to the session database to check for the state here and set the good value.
// But for now as it's behind a lab flag, will set to false and force initial sync when enabled
it.setBoolean(CryptoRoomEntityFields.SHOULD_SHARE_HISTORY, false)
}
realm.schema.get("OutboundGroupSessionInfoEntity")
?.addField(OutboundGroupSessionInfoEntityFields.SHOULD_SHARE_HISTORY, Boolean::class.java)?.transform {
// We don't have access to the session database to check for the state here and set the good value.
// But for now as it's behind a lab flag, will set to false and force initial sync when enabled
it.setBoolean(OutboundGroupSessionInfoEntityFields.SHOULD_SHARE_HISTORY, false)
}
realm.schema.get("CryptoMetadataEntity")
?.addField(CryptoMetadataEntityFields.ENABLE_KEY_FORWARDING_ON_INVITE, Boolean::class.java)
?.transform { obj ->
// default to false
obj.setBoolean(CryptoMetadataEntityFields.ENABLE_KEY_FORWARDING_ON_INVITE, false)
}
val moshiAdapter = MoshiProvider.providesMoshi().adapter(InboundGroupSessionData::class.java)
realm.schema.get("OlmInboundGroupSessionEntity")
?.addField(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, Boolean::class.java)
?.addField(OlmInboundGroupSessionEntityFields.ROOM_ID, String::class.java)
?.addField(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, String::class.java)
?.addField(OlmInboundGroupSessionEntityFields.SERIALIZED_OLM_INBOUND_GROUP_SESSION, String::class.java)
?.transform { dynamicObject ->
try {
// we want to convert the old wrapper frozen class into a
// map of sessionData & the pickled session herself
dynamicObject.getString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA)?.let { oldData ->
val oldWrapper = tryOrNull("Failed to convert megolm inbound group data") {
@Suppress("DEPRECATION")
deserializeFromRealm<org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2?>(oldData)
}
val groupSession = oldWrapper?.olmInboundGroupSession
?: return@transform Unit.also {
Timber.w("Failed to migrate megolm session, no olmInboundGroupSession")
}
// now convert to new data
val data = InboundGroupSessionData(
senderKey = oldWrapper.senderKey,
roomId = oldWrapper.roomId,
keysClaimed = oldWrapper.keysClaimed,
forwardingCurve25519KeyChain = oldWrapper.forwardingCurve25519KeyChain,
sharedHistory = false,
)
dynamicObject.setString(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, moshiAdapter.toJson(data))
dynamicObject.setString(OlmInboundGroupSessionEntityFields.SERIALIZED_OLM_INBOUND_GROUP_SESSION, serializeForRealm(groupSession))
// denormalized fields
dynamicObject.setString(OlmInboundGroupSessionEntityFields.ROOM_ID, oldWrapper.roomId)
dynamicObject.setBoolean(OlmInboundGroupSessionEntityFields.SHARED_HISTORY, false)
}
} catch (failure: Throwable) {
Timber.e(failure, "Failed to migrate megolm session")
}
}
}
}

View file

@ -1,52 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import timber.log.Timber
/**
* This migration is adding support for trusted flags on megolm sessions.
* We can't really assert the trust of existing keys, so for the sake of simplicity we are going to
* mark existing keys as safe.
* This migration can take long depending on the account
*/
internal class MigrateCryptoTo018(realm: DynamicRealm) : RealmMigrator(realm, 18) {
private val moshiAdapter = MoshiProvider.providesMoshi().adapter(InboundGroupSessionData::class.java)
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("OlmInboundGroupSessionEntity")
?.transform { dynamicObject ->
try {
dynamicObject.getString(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON)?.let { oldData ->
moshiAdapter.fromJson(oldData)?.let { dataToMigrate ->
dataToMigrate.copy(trusted = true).let {
dynamicObject.setString(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON, moshiAdapter.toJson(it))
}
}
}
} catch (failure: Throwable) {
Timber.e(failure, "Failed to migrate megolm session")
}
}
}
}

View file

@ -1,59 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import io.realm.DynamicRealmObject
import org.matrix.android.sdk.api.session.crypto.crosssigning.KeyUsage
import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
/**
* This migration is adding support for trusted flags on megolm sessions.
* We can't really assert the trust of existing keys, so for the sake of simplicity we are going to
* mark existing keys as safe.
* This migration can take long depending on the account
*/
internal class MigrateCryptoTo019(realm: DynamicRealm) : RealmMigrator(realm, 19) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("CrossSigningInfoEntity")
?.addField(CrossSigningInfoEntityFields.WAS_USER_VERIFIED_ONCE, Boolean::class.java)
?.transform { dynamicObject ->
val knowKeys = dynamicObject.getList(CrossSigningInfoEntityFields.CROSS_SIGNING_KEYS.`$`)
val msk = knowKeys.firstOrNull {
it.getList(KeyInfoEntityFields.USAGES.`$`, String::class.java).orEmpty().contains(KeyUsage.MASTER.value)
}
val ssk = knowKeys.firstOrNull {
it.getList(KeyInfoEntityFields.USAGES.`$`, String::class.java).orEmpty().contains(KeyUsage.SELF_SIGNING.value)
}
val isTrusted = isDynamicKeyInfoTrusted(msk?.get<DynamicRealmObject>(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`)) &&
isDynamicKeyInfoTrusted(ssk?.get<DynamicRealmObject>(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`))
dynamicObject.setBoolean(CrossSigningInfoEntityFields.WAS_USER_VERIFIED_ONCE, isTrusted)
}
}
private fun isDynamicKeyInfoTrusted(keyInfo: DynamicRealmObject?): Boolean {
if (keyInfo == null) return false
return !keyInfo.isNull(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED) && keyInfo.getBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED) &&
!keyInfo.isNull(TrustLevelEntityFields.LOCALLY_VERIFIED) && keyInfo.getBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED)
}
}

View file

@ -1,32 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
/**
* This migration adds a new field into MyDeviceLastSeenInfoEntity corresponding to the last seen user agent.
*/
internal class MigrateCryptoTo020(realm: DynamicRealm) : RealmMigrator(realm, 20) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("MyDeviceLastSeenInfoEntity")
?.addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_USER_AGENT, String::class.java)
}
}

View file

@ -1,43 +0,0 @@
/*
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.api.crypto.MEGOLM_DEFAULT_ROTATION_MSGS
import org.matrix.android.sdk.api.crypto.MEGOLM_DEFAULT_ROTATION_PERIOD_MS
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
/**
* This migration stores the rotation parameters for megolm oubound sessions.
*/
internal class MigrateCryptoTo021(realm: DynamicRealm) : RealmMigrator(realm, 21) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("CryptoRoomEntity")
?.addField(CryptoRoomEntityFields.ROTATION_PERIOD_MS, Long::class.java)
?.setNullable(CryptoRoomEntityFields.ROTATION_PERIOD_MS, true)
?.addField(CryptoRoomEntityFields.ROTATION_PERIOD_MSGS, Long::class.java)
?.setNullable(CryptoRoomEntityFields.ROTATION_PERIOD_MSGS, true)
?.transform {
// As a migration we set the default (will be on par with existing code)
// A clear cache will have the correct values.
it.setLong(CryptoRoomEntityFields.ROTATION_PERIOD_MS, MEGOLM_DEFAULT_ROTATION_PERIOD_MS)
it.setLong(CryptoRoomEntityFields.ROTATION_PERIOD_MSGS, MEGOLM_DEFAULT_ROTATION_MSGS)
}
}
}

View file

@ -1,49 +0,0 @@
/*
* Copyright 2023 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.RustEncryptionConfiguration
import org.matrix.android.sdk.internal.session.MigrateEAtoEROperation
import org.matrix.android.sdk.internal.util.database.RealmMigrator
import java.io.File
/**
* This migration creates the rust database and migrates from legacy crypto.
*/
internal class MigrateCryptoTo022(
realm: DynamicRealm,
private val rustDirectory: File,
private val rustEncryptionConfiguration: RustEncryptionConfiguration,
private val migrateMegolmGroupSessions: Boolean = false
) : RealmMigrator(
realm,
22
) {
override fun doMigrate(realm: DynamicRealm) {
// Migrate to rust!
val migrateOperation = MigrateEAtoEROperation(migrateMegolmGroupSessions)
migrateOperation.dynamicExecute(realm, rustDirectory, rustEncryptionConfiguration.getDatabasePassphrase())
// wa can't delete all for now, but we can do some cleaning
realm.schema.get("OlmSessionEntity")?.transform {
it.deleteFromRealm()
}
// a future migration will clean the rest
}
}

View file

@ -1,30 +0,0 @@
/*
* Copyright (c) 2024 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingKeyRequestEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
// Some fields are now required due to upgrade of Kotlin version.
// See https://github.com/realm/realm-java/issues/7810 for more details.
internal class MigrateCryptoTo023(realm: DynamicRealm) : RealmMigrator(realm, 23) {
override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("OutgoingKeyRequestEntity")
?.setRequired(OutgoingKeyRequestEntityFields.REQUEST_STATE_STR, true)
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2024 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration
import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.util.database.RealmMigrator
internal class MigrateCryptoTo024(realm: DynamicRealm) : RealmMigrator(realm, 24) {
/**
* Delete the whole DB, except tables that are still used to store data.
* Keep:
* - CryptoMetadataEntity
* - MyDeviceLastSeenInfoEntity
* - CryptoRoomEntity (but remove unused member 'outboundSessionInfo: OutboundGroupSessionInfoEntity')
*/
override fun doMigrate(realm: DynamicRealm) {
with(realm.schema) {
get("CryptoRoomEntity")?.removeField("outboundSessionInfo")
// Warning: order is important, first remove classes that depends on others.
remove("UserEntity")
remove("DeviceInfoEntity")
remove("CrossSigningInfoEntity")
remove("KeyInfoEntity")
remove("TrustLevelEntity")
remove("KeysBackupDataEntity")
remove("OlmInboundGroupSessionEntity")
remove("OlmSessionEntity")
remove("AuditTrailEntity")
remove("OutgoingKeyRequestEntity")
remove("KeyRequestReplyEntity")
remove("WithHeldSessionEntity")
remove("SharedSessionEntity")
remove("OutboundGroupSessionInfoEntity")
}
}
}

View file

@ -1,20 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration.rust
data class ExtractMigrationDataFailure(override val cause: Throwable) :
java.lang.RuntimeException("Can't proceed with migration, crypto store is empty or some necessary data is missing.", cause)

View file

@ -1,96 +0,0 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration.rust
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.kotlin.where
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntity
import org.matrix.olm.OlmUtility
import org.matrix.rustcomponents.sdk.crypto.MigrationData
import timber.log.Timber
import kotlin.system.measureTimeMillis
internal class ExtractMigrationDataUseCase(private val migrateGroupSessions: Boolean = false) {
fun extractData(realm: RealmToMigrate, importPartial: ((MigrationData) -> Unit)) {
return try {
extract(realm, importPartial)
} catch (failure: Throwable) {
throw ExtractMigrationDataFailure(failure)
}
}
fun hasExistingData(realmConfiguration: RealmConfiguration): Boolean {
return Realm.getInstance(realmConfiguration).use { realm ->
!realm.isEmpty &&
// Check if there is a MetaData object
realm.where<CryptoMetadataEntity>().count() > 0 &&
realm.where<CryptoMetadataEntity>().findFirst()?.olmAccountData != null
}
}
private fun extract(realm: RealmToMigrate, importPartial: ((MigrationData) -> Unit)) {
val pickleKey = OlmUtility.getRandomKey()
val baseExtract = realm.getPickledAccount(pickleKey)
// import the account asap
importPartial(baseExtract)
val chunkSize = 500
realm.trackedUsersChunk(500) {
importPartial(
baseExtract.copy(trackedUsers = it)
)
}
var migratedOlmSessionCount = 0
var writeTime = 0L
measureTimeMillis {
realm.pickledOlmSessions(pickleKey, chunkSize) { pickledSessions ->
migratedOlmSessionCount += pickledSessions.size
measureTimeMillis {
importPartial(
baseExtract.copy(sessions = pickledSessions)
)
}.also { writeTime += it }
}
}.also {
Timber.i("Migration: took $it ms to migrate $migratedOlmSessionCount olm sessions")
Timber.i("Migration: rust import time $writeTime")
}
// We don't migrate outbound session by default directly after migration
// We are going to do it lazyly when decryption fails
if (migrateGroupSessions) {
var migratedInboundGroupSessionCount = 0
measureTimeMillis {
realm.pickledOlmGroupSessions(pickleKey, chunkSize) { pickledSessions ->
migratedInboundGroupSessionCount += pickledSessions.size
measureTimeMillis {
importPartial(
baseExtract.copy(inboundGroupSessions = pickledSessions)
)
}.also { writeTime += it }
}
}.also {
Timber.i("Migration: took $it ms to migrate $migratedInboundGroupSessionCount group sessions")
Timber.i("Migration: rust import time $writeTime")
}
}
}
}

View file

@ -1,336 +0,0 @@
/*
* Copyright (c) 2023 The Matrix.org Foundation C.I.C.
*
* 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.store.db.migration.rust
import io.realm.kotlin.where
import okhttp3.internal.toImmutableList
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmInboundGroupSession
import org.matrix.olm.OlmSession
import org.matrix.rustcomponents.sdk.crypto.CrossSigningKeyExport
import org.matrix.rustcomponents.sdk.crypto.MigrationData
import org.matrix.rustcomponents.sdk.crypto.PickledAccount
import org.matrix.rustcomponents.sdk.crypto.PickledInboundGroupSession
import org.matrix.rustcomponents.sdk.crypto.PickledSession
import timber.log.Timber
import java.nio.charset.Charset
sealed class RealmToMigrate {
data class DynamicRealm(val realm: io.realm.DynamicRealm) : RealmToMigrate()
data class ClassicRealm(val realm: io.realm.Realm) : RealmToMigrate()
}
fun RealmToMigrate.hasExistingData(): Boolean {
return when (this) {
is RealmToMigrate.ClassicRealm -> {
!this.realm.isEmpty &&
// Check if there is a MetaData object
this.realm.where<CryptoMetadataEntity>().count() > 0 &&
this.realm.where<CryptoMetadataEntity>().findFirst()?.olmAccountData != null
}
is RealmToMigrate.DynamicRealm -> {
return true
}
}
}
@Throws
fun RealmToMigrate.getPickledAccount(pickleKey: ByteArray): MigrationData {
return when (this) {
is RealmToMigrate.ClassicRealm -> {
val metadataEntity = realm.where<CryptoMetadataEntity>().findFirst()
?: throw java.lang.IllegalArgumentException("Rust db migration: No existing metadataEntity")
val masterKey = metadataEntity.xSignMasterPrivateKey
val userKey = metadataEntity.xSignUserPrivateKey
val selfSignedKey = metadataEntity.xSignSelfSignedPrivateKey
Timber.i("## Migration: has private MSK ${masterKey.isNullOrBlank().not()}")
Timber.i("## Migration: has private USK ${userKey.isNullOrBlank().not()}")
Timber.i("## Migration: has private SSK ${selfSignedKey.isNullOrBlank().not()}")
val userId = metadataEntity.userId
?: throw java.lang.IllegalArgumentException("Rust db migration: userId is null")
val deviceId = metadataEntity.deviceId
?: throw java.lang.IllegalArgumentException("Rust db migration: deviceID is null")
val backupVersion = metadataEntity.backupVersion
val backupRecoveryKey = metadataEntity.keyBackupRecoveryKey
Timber.i("## Migration: has private backup key ${backupRecoveryKey != null} for version $backupVersion")
val isOlmAccountShared = metadataEntity.deviceKeysSentToServer
val olmAccount = metadataEntity.getOlmAccount()
?: throw java.lang.IllegalArgumentException("Rust db migration: No existing account")
val pickledOlmAccount = olmAccount.pickle(pickleKey, StringBuffer()).asString()
val pickledAccount = PickledAccount(
userId = userId,
deviceId = deviceId,
pickle = pickledOlmAccount,
shared = isOlmAccountShared,
uploadedSignedKeyCount = 50
)
MigrationData(
account = pickledAccount,
pickleKey = pickleKey,
crossSigning = CrossSigningKeyExport(
masterKey = masterKey,
selfSigningKey = selfSignedKey,
userSigningKey = userKey
),
sessions = emptyList(),
backupRecoveryKey = backupRecoveryKey,
trackedUsers = emptyList(),
inboundGroupSessions = emptyList(),
backupVersion = backupVersion,
// TODO import room settings from legacy DB
roomSettings = emptyMap()
)
}
is RealmToMigrate.DynamicRealm -> {
val cryptoMetadataEntitySchema = realm.schema.get("CryptoMetadataEntity")
?: throw java.lang.IllegalStateException("Missing Metadata entity")
var migrationData: MigrationData? = null
cryptoMetadataEntitySchema.transform { dynMetaData ->
val serializedOlmAccount = dynMetaData.getString(CryptoMetadataEntityFields.OLM_ACCOUNT_DATA)
val masterKey = dynMetaData.getString(CryptoMetadataEntityFields.X_SIGN_MASTER_PRIVATE_KEY)
val userKey = dynMetaData.getString(CryptoMetadataEntityFields.X_SIGN_USER_PRIVATE_KEY)
val selfSignedKey = dynMetaData.getString(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY)
val userId = dynMetaData.getString(CryptoMetadataEntityFields.USER_ID)
?: throw java.lang.IllegalArgumentException("Rust db migration: userId is null")
val deviceId = dynMetaData.getString(CryptoMetadataEntityFields.DEVICE_ID)
?: throw java.lang.IllegalArgumentException("Rust db migration: deviceID is null")
val backupVersion = dynMetaData.getString(CryptoMetadataEntityFields.BACKUP_VERSION)
val backupRecoveryKey = dynMetaData.getString(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY)
val isOlmAccountShared = dynMetaData.getBoolean(CryptoMetadataEntityFields.DEVICE_KEYS_SENT_TO_SERVER)
val olmAccount = deserializeFromRealm<OlmAccount>(serializedOlmAccount)
?: throw java.lang.IllegalArgumentException("Rust db migration: No existing account")
val pickledOlmAccount = olmAccount.pickle(pickleKey, StringBuffer()).asString()
val pickledAccount = PickledAccount(
userId = userId,
deviceId = deviceId,
pickle = pickledOlmAccount,
shared = isOlmAccountShared,
uploadedSignedKeyCount = 50
)
migrationData = MigrationData(
account = pickledAccount,
pickleKey = pickleKey,
crossSigning = CrossSigningKeyExport(
masterKey = masterKey,
selfSigningKey = selfSignedKey,
userSigningKey = userKey
),
sessions = emptyList(),
backupRecoveryKey = backupRecoveryKey,
trackedUsers = emptyList(),
inboundGroupSessions = emptyList(),
backupVersion = backupVersion,
// TODO import room settings from legacy DB
roomSettings = emptyMap()
)
}
migrationData!!
}
}
}
fun RealmToMigrate.trackedUsersChunk(chunkSize: Int, onChunk: ((List<String>) -> Unit)) {
when (this) {
is RealmToMigrate.ClassicRealm -> {
realm.where<UserEntity>()
.findAll()
.chunked(chunkSize)
.onEach {
onChunk(it.mapNotNull { it.userId })
}
}
is RealmToMigrate.DynamicRealm -> {
val userList = mutableListOf<String>()
realm.schema.get("UserEntity")?.transform {
val userId = it.getString(UserEntityFields.USER_ID)
// should we check the tracking status?
userList.add(userId)
if (userList.size > chunkSize) {
onChunk(userList.toImmutableList())
userList.clear()
}
}
if (userList.isNotEmpty()) {
onChunk(userList)
}
}
}
}
fun RealmToMigrate.pickledOlmSessions(pickleKey: ByteArray, chunkSize: Int, onChunk: ((List<PickledSession>) -> Unit)) {
when (this) {
is RealmToMigrate.ClassicRealm -> {
realm.where<OlmSessionEntity>().findAll()
.chunked(chunkSize) { chunk ->
val export = chunk.map { it.toPickledSession(pickleKey) }
onChunk(export)
}
}
is RealmToMigrate.DynamicRealm -> {
val pickledSessions = mutableListOf<PickledSession>()
realm.schema.get("OlmSessionEntity")?.transform {
val sessionData = it.getString(OlmSessionEntityFields.OLM_SESSION_DATA)
val deviceKey = it.getString(OlmSessionEntityFields.DEVICE_KEY)
val lastReceivedMessageTs = it.getLong(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS)
val olmSession = deserializeFromRealm<OlmSession>(sessionData)!!
val pickle = olmSession.pickle(pickleKey, StringBuffer()).asString()
val pickledSession = PickledSession(
pickle = pickle,
senderKey = deviceKey,
createdUsingFallbackKey = false,
// / Unix timestamp (in seconds) when the session was created.
creationTime = (lastReceivedMessageTs / 1000).toULong(),
// / Unix timestamp (in seconds) when the session was last used.
lastUseTime = (lastReceivedMessageTs / 1000).toULong(),
)
// should we check the tracking status?
pickledSessions.add(pickledSession)
if (pickledSessions.size > chunkSize) {
onChunk(pickledSessions.toImmutableList())
pickledSessions.clear()
}
}
if (pickledSessions.isNotEmpty()) {
onChunk(pickledSessions)
}
}
}
}
private val sessionDataAdapter = MoshiProvider.providesMoshi()
.adapter(InboundGroupSessionData::class.java)
fun RealmToMigrate.pickledOlmGroupSessions(pickleKey: ByteArray, chunkSize: Int, onChunk: ((List<PickledInboundGroupSession>) -> Unit)) {
when (this) {
is RealmToMigrate.ClassicRealm -> {
realm.where<OlmInboundGroupSessionEntity>()
.findAll()
.chunked(chunkSize) { chunk ->
val export = chunk.mapNotNull { it.toPickledInboundGroupSession(pickleKey) }
onChunk(export)
}
}
is RealmToMigrate.DynamicRealm -> {
val pickledSessions = mutableListOf<PickledInboundGroupSession>()
realm.schema.get("OlmInboundGroupSessionEntity")?.transform {
val senderKey = it.getString(OlmInboundGroupSessionEntityFields.SENDER_KEY)
val roomId = it.getString(OlmInboundGroupSessionEntityFields.ROOM_ID)
val backedUp = it.getBoolean(OlmInboundGroupSessionEntityFields.BACKED_UP)
val serializedOlmInboundGroupSession = it.getString(OlmInboundGroupSessionEntityFields.SERIALIZED_OLM_INBOUND_GROUP_SESSION)
val inboundSession = deserializeFromRealm<OlmInboundGroupSession>(serializedOlmInboundGroupSession) ?: return@transform Unit.also {
Timber.w("Rust db migration: Failed to migrated group session, no meta data")
}
val sessionData = it.getString(OlmInboundGroupSessionEntityFields.INBOUND_GROUP_SESSION_DATA_JSON).let { json ->
sessionDataAdapter.fromJson(json)
} ?: return@transform Unit.also {
Timber.w("Rust db migration: Failed to migrated group session, no meta data")
}
val pickle = inboundSession.pickle(pickleKey, StringBuffer()).asString()
val pickledSession = PickledInboundGroupSession(
pickle = pickle,
senderKey = senderKey,
signingKey = sessionData.keysClaimed.orEmpty(),
roomId = roomId,
forwardingChains = sessionData.forwardingCurve25519KeyChain.orEmpty(),
imported = sessionData.trusted.orFalse().not(),
backedUp = backedUp
)
// should we check the tracking status?
pickledSessions.add(pickledSession)
if (pickledSessions.size > chunkSize) {
onChunk(pickledSessions.toImmutableList())
pickledSessions.clear()
}
}
if (pickledSessions.isNotEmpty()) {
onChunk(pickledSessions)
}
}
}
}
private fun OlmInboundGroupSessionEntity.toPickledInboundGroupSession(pickleKey: ByteArray): PickledInboundGroupSession? {
val senderKey = this.senderKey ?: return null
val backedUp = this.backedUp
val olmInboundGroupSession = this.getOlmGroupSession() ?: return null.also {
Timber.w("Rust db migration: Failed to migrated group session $sessionId")
}
val data = this.getData() ?: return null.also {
Timber.w("Rust db migration: Failed to migrated group session $sessionId, no meta data")
}
val roomId = data.roomId ?: return null.also {
Timber.w("Rust db migration: Failed to migrated group session $sessionId, no roomId")
}
val pickledInboundGroupSession = olmInboundGroupSession.pickle(pickleKey, StringBuffer()).asString()
return PickledInboundGroupSession(
pickle = pickledInboundGroupSession,
senderKey = senderKey,
signingKey = data.keysClaimed.orEmpty(),
roomId = roomId,
forwardingChains = data.forwardingCurve25519KeyChain.orEmpty(),
imported = data.trusted.orFalse().not(),
backedUp = backedUp
)
}
private fun OlmSessionEntity.toPickledSession(pickleKey: ByteArray): PickledSession {
val deviceKey = this.deviceKey ?: ""
val lastReceivedMessageTs = this.lastReceivedMessageTs
val olmSessionStr = this.olmSessionData
val olmSession = deserializeFromRealm<OlmSession>(olmSessionStr)!!
val pickledOlmSession = olmSession.pickle(pickleKey, StringBuffer()).asString()
return PickledSession(
pickle = pickledOlmSession,
senderKey = deviceKey,
createdUsingFallbackKey = false,
// Rust expect in seconds
creationTime = (lastReceivedMessageTs / 1000).toULong(),
// Rust expect in seconds
lastUseTime = (lastReceivedMessageTs / 1000).toULong(),
)
}
private val charset = Charset.forName("UTF-8")
private fun ByteArray.asString() = String(this, charset)

View file

@ -1,28 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.model
import io.realm.RealmObject
import io.realm.annotations.Index
internal open class AuditTrailEntity(
var ageLocalTs: Long? = null,
@Index var type: String? = null,
var contentJson: String? = null
) : RealmObject() {
companion object
}

View file

@ -1,82 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.model
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
import org.matrix.android.sdk.api.session.crypto.model.ForwardInfo
import org.matrix.android.sdk.api.session.crypto.model.IncomingKeyRequestInfo
import org.matrix.android.sdk.api.session.crypto.model.TrailType
import org.matrix.android.sdk.api.session.crypto.model.UnknownInfo
import org.matrix.android.sdk.api.session.crypto.model.WithheldInfo
import org.matrix.android.sdk.internal.di.MoshiProvider
internal object AuditTrailMapper {
fun map(entity: AuditTrailEntity): AuditTrail? {
val contentJson = entity.contentJson ?: return null
return when (entity.type) {
TrailType.OutgoingKeyForward.name -> {
val info = tryOrNull {
MoshiProvider.providesMoshi().adapter(ForwardInfo::class.java).fromJson(contentJson)
} ?: return null
AuditTrail(
ageLocalTs = entity.ageLocalTs ?: 0,
type = TrailType.OutgoingKeyForward,
info = info
)
}
TrailType.OutgoingKeyWithheld.name -> {
val info = tryOrNull {
MoshiProvider.providesMoshi().adapter(WithheldInfo::class.java).fromJson(contentJson)
} ?: return null
AuditTrail(
ageLocalTs = entity.ageLocalTs ?: 0,
type = TrailType.OutgoingKeyWithheld,
info = info
)
}
TrailType.IncomingKeyRequest.name -> {
val info = tryOrNull {
MoshiProvider.providesMoshi().adapter(IncomingKeyRequestInfo::class.java).fromJson(contentJson)
} ?: return null
AuditTrail(
ageLocalTs = entity.ageLocalTs ?: 0,
type = TrailType.IncomingKeyRequest,
info = info
)
}
TrailType.IncomingKeyForward.name -> {
val info = tryOrNull {
MoshiProvider.providesMoshi().adapter(ForwardInfo::class.java).fromJson(contentJson)
} ?: return null
AuditTrail(
ageLocalTs = entity.ageLocalTs ?: 0,
type = TrailType.IncomingKeyForward,
info = info
)
}
else -> {
AuditTrail(
ageLocalTs = entity.ageLocalTs ?: 0,
type = TrailType.Unknown,
info = UnknownInfo
)
}
}
}
}

View file

@ -1,65 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.store.db.model
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import org.matrix.android.sdk.api.session.crypto.crosssigning.KeyUsage
import org.matrix.android.sdk.internal.extensions.clearWith
internal open class CrossSigningInfoEntity(
@PrimaryKey
var userId: String? = null,
var wasUserVerifiedOnce: Boolean = false,
var crossSigningKeys: RealmList<KeyInfoEntity> = RealmList()
) : RealmObject() {
companion object
fun getMasterKey() = crossSigningKeys.firstOrNull { it.usages.contains(KeyUsage.MASTER.value) }
fun setMasterKey(info: KeyInfoEntity?) {
crossSigningKeys
.filter { it.usages.contains(KeyUsage.MASTER.value) }
.forEach { crossSigningKeys.remove(it) }
info?.let { crossSigningKeys.add(it) }
}
fun getSelfSignedKey() = crossSigningKeys.firstOrNull { it.usages.contains(KeyUsage.SELF_SIGNING.value) }
fun setSelfSignedKey(info: KeyInfoEntity?) {
crossSigningKeys
.filter { it.usages.contains(KeyUsage.SELF_SIGNING.value) }
.forEach { crossSigningKeys.remove(it) }
info?.let { crossSigningKeys.add(it) }
}
fun getUserSigningKey() = crossSigningKeys.firstOrNull { it.usages.contains(KeyUsage.USER_SIGNING.value) }
fun setUserSignedKey(info: KeyInfoEntity?) {
crossSigningKeys
.filter { it.usages.contains(KeyUsage.USER_SIGNING.value) }
.forEach { crossSigningKeys.remove(it) }
info?.let { crossSigningKeys.add(it) }
}
}
internal fun CrossSigningInfoEntity.deleteOnCascade() {
crossSigningKeys.clearWith { it.deleteOnCascade() }
deleteFromRealm()
}

View file

@ -1,123 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.store.db.model
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.di.SerializeNulls
import timber.log.Timber
internal object CryptoMapper {
private val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
private val listMigrationAdapter = moshi.adapter<List<String>>(
Types.newParameterizedType(
List::class.java,
String::class.java,
Any::class.java
)
)
private val mapMigrationAdapter = moshi.adapter<JsonDict>(
Types.newParameterizedType(
Map::class.java,
String::class.java,
Any::class.java
)
)
private val mapOfStringMigrationAdapter = moshi.adapter<Map<String, Map<String, String>>>(
Types.newParameterizedType(
Map::class.java,
String::class.java,
Any::class.java
)
)
internal fun mapToEntity(deviceInfo: CryptoDeviceInfo): DeviceInfoEntity {
return DeviceInfoEntity(primaryKey = DeviceInfoEntity.createPrimaryKey(deviceInfo.userId, deviceInfo.deviceId))
.also { updateDeviceInfoEntity(it, deviceInfo) }
}
internal fun updateDeviceInfoEntity(entity: DeviceInfoEntity, deviceInfo: CryptoDeviceInfo) {
entity.userId = deviceInfo.userId
entity.deviceId = deviceInfo.deviceId
entity.algorithmListJson = listMigrationAdapter.toJson(deviceInfo.algorithms)
entity.keysMapJson = mapMigrationAdapter.toJson(deviceInfo.keys)
entity.signatureMapJson = mapMigrationAdapter.toJson(deviceInfo.signatures)
entity.isBlocked = deviceInfo.isBlocked
val deviceInfoTrustLevel = deviceInfo.trustLevel
if (deviceInfoTrustLevel == null) {
entity.trustLevelEntity?.deleteFromRealm()
entity.trustLevelEntity = null
} else {
if (entity.trustLevelEntity == null) {
// Create a new TrustLevelEntity object
entity.trustLevelEntity = TrustLevelEntity()
}
// Update the existing TrustLevelEntity object
entity.trustLevelEntity?.crossSignedVerified = deviceInfoTrustLevel.crossSigningVerified
entity.trustLevelEntity?.locallyVerified = deviceInfoTrustLevel.locallyVerified
}
// We store the device name if present now
entity.unsignedMapJson = deviceInfo.unsigned?.deviceDisplayName
}
internal fun mapToModel(deviceInfoEntity: DeviceInfoEntity): CryptoDeviceInfo {
return CryptoDeviceInfo(
userId = deviceInfoEntity.userId ?: "",
deviceId = deviceInfoEntity.deviceId ?: "",
isBlocked = deviceInfoEntity.isBlocked ?: false,
trustLevel = deviceInfoEntity.trustLevelEntity?.let {
DeviceTrustLevel(it.crossSignedVerified ?: false, it.locallyVerified)
},
unsigned = deviceInfoEntity.unsignedMapJson?.let { UnsignedDeviceInfo(deviceDisplayName = it) },
signatures = deviceInfoEntity.signatureMapJson?.let {
try {
mapOfStringMigrationAdapter.fromJson(it)
} catch (failure: Throwable) {
Timber.e(failure)
null
}
},
keys = deviceInfoEntity.keysMapJson?.let {
try {
moshi.adapter<Map<String, String>>(
Types.newParameterizedType(
Map::class.java,
String::class.java,
Any::class.java
)
).fromJson(it)
} catch (failure: Throwable) {
Timber.e(failure)
null
}
},
algorithms = deviceInfoEntity.algorithmListJson?.let {
try {
listMigrationAdapter.fromJson(it)
} catch (failure: Throwable) {
Timber.e(failure)
null
}
},
firstTimeSeenLocalTs = deviceInfoEntity.firstTimeSeenLocalTs
)
}
}

View file

@ -18,9 +18,6 @@ package org.matrix.android.sdk.internal.crypto.store.db.model
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
import org.matrix.olm.OlmAccount
internal open class CryptoMetadataEntity(
// The current user id.
@ -53,15 +50,4 @@ internal open class CryptoMetadataEntity(
var keyBackupRecoveryKeyVersion: String? = null
// var crossSigningInfoEntity: CrossSigningInfoEntity? = null
) : RealmObject() {
// Deserialize data
fun getOlmAccount(): OlmAccount? {
return deserializeFromRealm(olmAccountData)
}
// Serialize data
fun putOlmAccount(olmAccount: OlmAccount?) {
olmAccountData = serializeForRealm(olmAccount)
}
}
) : RealmObject()

View file

@ -26,10 +26,6 @@ internal open class CryptoRoomEntity(
var blacklistUnverifiedDevices: Boolean = false,
// Determines whether or not room history should be shared on new member invites
var shouldShareHistory: Boolean = false,
// Store the current outbound session for this room,
// to avoid re-create and re-share at each startup (if rotation not needed..)
// This is specific to megolm but not sure how to model it better
var outboundSessionInfo: OutboundGroupSessionInfoEntity? = null,
// a security to ensure that a room will never revert to not encrypted
// even if a new state event with empty encryption, or state is reset somehow
var wasEncryptedOnce: Boolean? = false,

View file

@ -1,54 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.store.db.model
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
internal fun DeviceInfoEntity.Companion.createPrimaryKey(userId: String, deviceId: String) = "$userId|$deviceId"
internal open class DeviceInfoEntity(
@PrimaryKey var primaryKey: String = "",
var deviceId: String? = null,
var identityKey: String? = null,
var userId: String? = null,
var isBlocked: Boolean? = null,
var algorithmListJson: String? = null,
var keysMapJson: String? = null,
var signatureMapJson: String? = null,
// Will contain the device name from unsigned data if present
var unsignedMapJson: String? = null,
var trustLevelEntity: TrustLevelEntity? = null,
/**
* We use that to make distinction between old devices (there before mine)
* and new ones. Used for example to detect new unverified login
*/
var firstTimeSeenLocalTs: Long? = null
) : RealmObject() {
@LinkingObjects("devices")
val users: RealmResults<UserEntity>? = null
companion object
}
internal fun DeviceInfoEntity.deleteOnCascade() {
trustLevelEntity?.deleteFromRealm()
deleteFromRealm()
}

View file

@ -1,37 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.store.db.model
import io.realm.RealmList
import io.realm.RealmObject
internal open class KeyInfoEntity(
var publicKeyBase64: String? = null,
// var isTrusted: Boolean = false,
var usages: RealmList<String> = RealmList(),
/**
* The signature of this MXDeviceInfo.
* A map from "<userId>" to a map from "<key type>:<Publickey>" to "<signature>"
*/
var signatures: String? = null,
var trustLevelEntity: TrustLevelEntity? = null
) : RealmObject()
internal fun KeyInfoEntity.deleteOnCascade() {
trustLevelEntity?.deleteFromRealm()
deleteFromRealm()
}

View file

@ -1,35 +0,0 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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.store.db.model
import io.realm.RealmObject
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.internal.di.MoshiProvider
internal open class KeyRequestReplyEntity(
var senderId: String? = null,
var fromDevice: String? = null,
var eventJson: String? = null
) : RealmObject() {
companion object
fun getEvent(): Event? {
return eventJson?.let {
MoshiProvider.providesMoshi().adapter(Event::class.java).fromJson(it)
}
}
}

View file

@ -1,30 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.store.db.model
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
internal open class KeysBackupDataEntity(
// Primary key to update this object. There is only one object, so it's a constant, please do not set it
@PrimaryKey
var primaryKey: Int = 0,
// The last known hash of the backed up keys on the server
var backupLastServerHash: String? = null,
// The last known number of backed up keys on the server
var backupLastServerNumberOfKeys: Int? = null
) : RealmObject()

View file

@ -1,113 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* 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.store.db.model
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
import org.matrix.android.sdk.internal.crypto.model.InboundGroupSessionData
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.olm.OlmInboundGroupSession
import timber.log.Timber
internal fun OlmInboundGroupSessionEntity.Companion.createPrimaryKey(sessionId: String?, senderKey: String?) = "$sessionId|$senderKey"
internal open class OlmInboundGroupSessionEntity(
// Combined value to build a primary key
@PrimaryKey var primaryKey: String? = null,
// denormalization for faster querying (these fields are in the inboundGroupSessionDataJson)
var sessionId: String? = null,
var senderKey: String? = null,
var roomId: String? = null,
// Deprecated, used for migration / olmInboundGroupSessionData contains Json
// keep it in case of problem to have a chance to recover
var olmInboundGroupSessionData: String? = null,
// Stores the session data in an extensible format
// to allow to store data not yet supported for later use
var inboundGroupSessionDataJson: String? = null,
// The pickled session
var serializedOlmInboundGroupSession: String? = null,
// Flag that indicates whether or not the current inboundSession will be shared to
// invited users to decrypt past messages
var sharedHistory: Boolean = false,
// Indicate if the key has been backed up to the homeserver
var backedUp: Boolean = false
) :
RealmObject() {
fun store(wrapper: MXInboundMegolmSessionWrapper) {
this.serializedOlmInboundGroupSession = serializeForRealm(wrapper.session)
this.inboundGroupSessionDataJson = adapter.toJson(wrapper.sessionData)
this.roomId = wrapper.sessionData.roomId
this.senderKey = wrapper.sessionData.senderKey
this.sessionId = wrapper.session.sessionIdentifier()
this.sharedHistory = wrapper.sessionData.sharedHistory
}
// fun getInboundGroupSession(): OlmInboundGroupSessionWrapper2? {
// return try {
// deserializeFromRealm<OlmInboundGroupSessionWrapper2?>(olmInboundGroupSessionData)
// } catch (failure: Throwable) {
// Timber.e(failure, "## Deserialization failure")
// return null
// }
// }
//
// fun putInboundGroupSession(olmInboundGroupSessionWrapper: OlmInboundGroupSessionWrapper2?) {
// olmInboundGroupSessionData = serializeForRealm(olmInboundGroupSessionWrapper)
// }
fun getOlmGroupSession(): OlmInboundGroupSession? {
return try {
deserializeFromRealm(serializedOlmInboundGroupSession)
} catch (failure: Throwable) {
Timber.e(failure, "## Deserialization failure")
return null
}
}
fun getData(): InboundGroupSessionData? {
return try {
inboundGroupSessionDataJson?.let {
adapter.fromJson(it)
}
} catch (failure: Throwable) {
Timber.e(failure, "## Deserialization failure")
return null
}
}
fun toModel(): MXInboundMegolmSessionWrapper? {
val data = getData() ?: return null
val session = getOlmGroupSession() ?: return null
return MXInboundMegolmSessionWrapper(
session = session,
sessionData = data
)
}
companion object {
private val adapter = MoshiProvider.providesMoshi()
.adapter(InboundGroupSessionData::class.java)
}
}

Some files were not shown because too many files have changed in this diff Show more