mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-21 17:05:39 +03:00
Merge branch 'release/1.6.22' into main
This commit is contained in:
commit
9a4602bfd4
164 changed files with 394 additions and 7611 deletions
14
CHANGES.md
14
CHANGES.md
|
@ -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)
|
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))
|
- 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))
|
- 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
|
Other changes
|
||||||
-------------
|
-------------
|
||||||
|
|
|
@ -96,9 +96,9 @@ allprojects {
|
||||||
}
|
}
|
||||||
// Jitsi repo
|
// Jitsi repo
|
||||||
maven {
|
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:
|
// 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 {
|
content {
|
||||||
groups.jitsi.regex.each { includeGroupByRegex it }
|
groups.jitsi.regex.each { includeGroupByRegex it }
|
||||||
groups.jitsi.group.each { includeGroup it }
|
groups.jitsi.group.each { includeGroup it }
|
||||||
|
|
|
@ -72,6 +72,7 @@ ext.groups = [
|
||||||
'com.facebook.flipper',
|
'com.facebook.flipper',
|
||||||
'com.facebook.fresco',
|
'com.facebook.fresco',
|
||||||
'com.facebook.infer.annotation',
|
'com.facebook.infer.annotation',
|
||||||
|
'com.facebook.react',
|
||||||
'com.facebook.soloader',
|
'com.facebook.soloader',
|
||||||
'com.facebook.stetho',
|
'com.facebook.stetho',
|
||||||
'com.facebook.yoga',
|
'com.facebook.yoga',
|
||||||
|
|
|
@ -28,18 +28,18 @@ The generated maven repository is then host in the project https://github.com/el
|
||||||
|
|
||||||
#### Jitsi version
|
#### 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
|
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
|
#### Run the build script
|
||||||
|
|
||||||
At the root of the Element Android, run the following script:
|
At the root of the Element Android, run the following script:
|
||||||
|
|
||||||
```shell 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`
|
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:
|
- Update the file `./build.gradle` to use the previously created local Maven repository. Currently we have this line:
|
||||||
|
|
||||||
```groovy
|
```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.
|
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:
|
- Update the dependency of the Jitsi Meet library in the file `./vector/build.gradle`. Currently we have this line:
|
||||||
|
|
||||||
```groovy
|
```groovy
|
||||||
api('org.jitsi.react:jitsi-meet-sdk:8.1.1')
|
api('org.jitsi.react:jitsi-meet-sdk:10.2.0')
|
||||||
```
|
|
||||||
|
|
||||||
- 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')
|
|
||||||
```
|
```
|
||||||
|
|
||||||
- Perform a gradle sync and build the project
|
- 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:
|
- Update the file `./build.gradle` to use the previously created Maven repository. Currently we have this line:
|
||||||
|
|
||||||
```groovy
|
```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.
|
- Build the project and perform the sanity tests again.
|
||||||
|
|
2
fastlane/metadata/android/en-US/changelogs/40106220.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40106220.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: crypto sdk upgrade.
|
||||||
|
Full changelog: https://github.com/element-hq/element-android/releases
|
|
@ -2966,4 +2966,4 @@
|
||||||
\n%s</string>
|
\n%s</string>
|
||||||
<string name="create_room_unknown_users_dialog_submit">آغاز گپ به هر حال</string>
|
<string name="create_room_unknown_users_dialog_submit">آغاز گپ به هر حال</string>
|
||||||
<string name="invite_unknown_users_dialog_submit">دعوت به هر حال</string>
|
<string name="invite_unknown_users_dialog_submit">دعوت به هر حال</string>
|
||||||
</resources>
|
</resources>
|
|
@ -2865,4 +2865,4 @@
|
||||||
<string name="room_polls_wait_for_display">显示投票</string>
|
<string name="room_polls_wait_for_display">显示投票</string>
|
||||||
<string name="rich_text_editor_quote">切换引用</string>
|
<string name="rich_text_editor_quote">切换引用</string>
|
||||||
<string name="rich_text_editor_numbered_list">切换有序列表</string>
|
<string name="rich_text_editor_numbered_list">切换有序列表</string>
|
||||||
</resources>
|
</resources>
|
|
@ -1175,9 +1175,9 @@
|
||||||
|
|
||||||
<!-- room settings : alias -->
|
<!-- room settings : alias -->
|
||||||
<string name="room_settings_alias_title">Room addresses</string>
|
<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_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_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>
|
<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 don’t know your password, go back to reset it.</string>
|
<string name="login_signin_matrix_id_password_notice">If you don’t 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="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="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>
|
<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_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_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_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>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<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>
|
<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_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_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>
|
<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_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="onboarding_new_app_layout_button_try">Try it out</string>
|
||||||
|
|
||||||
<string name="one">1</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="two">2</string>
|
<string tools:ignore="UnusedResources" name="one">1</string>
|
||||||
<string name="three">3</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 -->
|
<!-- QR Code Login -->
|
||||||
<string name="qr_code_login_header_scan_qr_code_title">Scan QR code</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<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 tools:ignore="UnusedResources" name="qr_code_login_header_scan_qr_code_title">Scan QR code</string>
|
||||||
<string name="qr_code_login_header_show_qr_code_title">Sign in with QR code</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<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 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>
|
||||||
<string name="qr_code_login_header_show_qr_code_link_a_device_description">Scan the QR code below with your device that’s signed out.</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="qr_code_login_header_connected_title">Secure connection established</string>
|
<string tools:ignore="UnusedResources" name="qr_code_login_header_show_qr_code_title">Sign in with QR code</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>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="qr_code_login_header_failed_title">Unsuccessful connection</string>
|
<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>
|
||||||
<string name="qr_code_login_header_failed_device_is_not_supported_description">Linking with this device is not supported.</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="qr_code_login_header_failed_timeout_description">The linking wasn’t completed in the required time.</string>
|
<string tools:ignore="UnusedResources" name="qr_code_login_header_show_qr_code_link_a_device_description">Scan the QR code below with your device that’s signed out.</string>
|
||||||
<string name="qr_code_login_header_failed_denied_description">The request was denied on the other device.</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="qr_code_login_header_failed_other_description">The request failed.</string>
|
<string tools:ignore="UnusedResources" name="qr_code_login_header_connected_title">Secure connection established</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>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="qr_code_login_header_failed_other_device_already_signed_in_description">The other device is already signed in.</string>
|
<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>
|
||||||
<string 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 name="qr_code_login_header_failed_invalid_qr_code_description">That QR code is invalid.</string>
|
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_title">Unsuccessful connection</string>
|
||||||
<string name="qr_code_login_header_failed_user_cancelled_description">The sign in was cancelled on the other device.</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="qr_code_login_header_failed_homeserver_is_not_supported_description">The homeserver doesn\'t support sign in with QR code.</string>
|
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_device_is_not_supported_description">Linking with this device is not supported.</string>
|
||||||
<string name="qr_code_login_new_device_instruction_1">Open the app on your other device</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="qr_code_login_new_device_instruction_2">Go to Settings -> Security & Privacy</string>
|
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_timeout_description">The linking wasn’t completed in the required time.</string>
|
||||||
<string name="qr_code_login_new_device_instruction_3">Select \'Show QR code\'</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="qr_code_login_link_a_device_scan_qr_code_instruction_1">Start at the sign in screen</string>
|
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_denied_description">The request was denied on the other device.</string>
|
||||||
<string name="qr_code_login_link_a_device_scan_qr_code_instruction_2">Select \'Sign in with QR code\'</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="qr_code_login_link_a_device_show_qr_code_instruction_1">Start at the sign in screen</string>
|
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_other_description">The request failed.</string>
|
||||||
<string name="qr_code_login_link_a_device_show_qr_code_instruction_2">Select \'Scan QR code\'</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="qr_code_login_show_qr_code_button">Show QR code in this device</string>
|
<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>
|
||||||
<string name="qr_code_login_signing_in_a_mobile_device">Signing in a mobile device?</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="qr_code_login_scan_qr_code_button">Scan QR code</string>
|
<string tools:ignore="UnusedResources" 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_connecting_to_device">Connecting to device</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="qr_code_login_signing_in">Signing you in</string>
|
<string tools:ignore="UnusedResources" 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_status_no_match">No match?</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="qr_code_login_try_again">Try again</string>
|
<string tools:ignore="UnusedResources" name="qr_code_login_header_failed_invalid_qr_code_description">That QR code is invalid.</string>
|
||||||
<string name="qr_code_login_confirm_security_code">Confirm</string>
|
<!-- TODO TO BE REMOVED -->
|
||||||
<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>
|
<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 & 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 -->
|
<!-- Rich text editor -->
|
||||||
<string name="rich_text_editor_format_bold">Apply bold format</string>
|
<string name="rich_text_editor_format_bold">Apply bold format</string>
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -62,7 +62,7 @@ android {
|
||||||
// that the app's state is completely cleared between tests.
|
// that the app's state is completely cleared between tests.
|
||||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
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", "\"${gitRevision()}\""
|
||||||
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
||||||
|
@ -205,9 +205,6 @@ dependencies {
|
||||||
// Work
|
// Work
|
||||||
implementation libs.androidx.work
|
implementation libs.androidx.work
|
||||||
|
|
||||||
// olm lib is now hosted in MavenCentral
|
|
||||||
implementation 'org.matrix.android:olm-sdk:3.2.12'
|
|
||||||
|
|
||||||
// DI
|
// DI
|
||||||
implementation libs.dagger.dagger
|
implementation libs.dagger.dagger
|
||||||
kapt libs.dagger.daggerCompiler
|
kapt libs.dagger.daggerCompiler
|
||||||
|
@ -224,7 +221,7 @@ dependencies {
|
||||||
|
|
||||||
implementation libs.google.phonenumber
|
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")
|
// api project(":library:rustCrypto")
|
||||||
|
|
||||||
testImplementation libs.tests.junit
|
testImplementation libs.tests.junit
|
||||||
|
@ -236,6 +233,7 @@ dependencies {
|
||||||
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
|
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
|
||||||
// Transitively required for mocking realm as monarchy doesn't expose Rx
|
// Transitively required for mocking realm as monarchy doesn't expose Rx
|
||||||
testImplementation libs.rx.rxKotlin
|
testImplementation libs.rx.rxKotlin
|
||||||
|
testImplementation libs.tests.robolectric
|
||||||
|
|
||||||
kaptAndroidTest libs.dagger.daggerCompiler
|
kaptAndroidTest libs.dagger.daggerCompiler
|
||||||
androidTestImplementation libs.androidx.testCore
|
androidTestImplementation libs.androidx.testCore
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:a7acd69f37612bab0a1ab7f456656712d7ba19dbb679f81b97b58ef44e239f42
|
|
||||||
size 8523776
|
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:59b4957aa2f9cdc17b14ec8546e144537fac9dee050c6eb173f56fa8602c2736
|
|
||||||
size 2097152
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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.network.UserAgentHolder
|
||||||
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
|
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
|
||||||
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
|
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
|
||||||
import org.matrix.olm.OlmManager
|
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import javax.inject.Inject
|
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 rawService: RawService
|
||||||
@Inject internal lateinit var userAgentHolder: UserAgentHolder
|
@Inject internal lateinit var userAgentHolder: UserAgentHolder
|
||||||
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
||||||
@Inject internal lateinit var olmManager: OlmManager
|
|
||||||
@Inject internal lateinit var sessionManager: SessionManager
|
@Inject internal lateinit var sessionManager: SessionManager
|
||||||
@Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
|
@Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
|
||||||
@Inject internal lateinit var apiInterceptor: ApiInterceptor
|
@Inject internal lateinit var apiInterceptor: ApiInterceptor
|
||||||
|
|
|
@ -20,7 +20,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import org.junit.Assert.assertArrayEquals
|
import org.junit.Assert.assertArrayEquals
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
@ -29,19 +28,12 @@ import org.junit.runners.MethodSorters
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||||
import org.matrix.android.sdk.common.assertByteArrayNotEqual
|
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")
|
@Ignore("Ignored in order to speed up test run time")
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
class KeysBackupPasswordTest : InstrumentedTest {
|
class KeysBackupPasswordTest : InstrumentedTest {
|
||||||
|
|
||||||
@Before
|
|
||||||
fun ensureLibLoaded() {
|
|
||||||
OlmManager()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check KeysBackupPassword utilities
|
* Check KeysBackupPassword utilities
|
||||||
*/
|
*/
|
||||||
|
@ -51,7 +43,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
|
||||||
|
|
||||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, generatePrivateKeyResult.privateKey.size)
|
||||||
|
|
||||||
// Reverse operation
|
// Reverse operation
|
||||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
|
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
|
||||||
|
@ -60,7 +52,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
|
||||||
generatePrivateKeyResult.iterations
|
generatePrivateKeyResult.iterations
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, retrievedPrivateKey.size)
|
||||||
assertArrayEquals(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
assertArrayEquals(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +93,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
|
||||||
|
|
||||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, generatePrivateKeyResult.privateKey.size)
|
||||||
|
|
||||||
// Reverse operation, with bad password
|
// Reverse operation, with bad password
|
||||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
|
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
|
||||||
|
@ -110,7 +102,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
|
||||||
generatePrivateKeyResult.iterations
|
generatePrivateKeyResult.iterations
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, retrievedPrivateKey.size)
|
||||||
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +115,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
|
||||||
|
|
||||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, generatePrivateKeyResult.privateKey.size)
|
||||||
|
|
||||||
// Reverse operation, with bad iteration
|
// Reverse operation, with bad iteration
|
||||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
|
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
|
||||||
|
@ -132,7 +124,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
|
||||||
500_001
|
500_001
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, retrievedPrivateKey.size)
|
||||||
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,7 +137,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
|
||||||
|
|
||||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, generatePrivateKeyResult.privateKey.size)
|
||||||
|
|
||||||
// Reverse operation, with bad iteration
|
// Reverse operation, with bad iteration
|
||||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
|
val retrievedPrivateKey = retrievePrivateKeyWithPassword(
|
||||||
|
@ -154,7 +146,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
|
||||||
generatePrivateKeyResult.iterations
|
generatePrivateKeyResult.iterations
|
||||||
)
|
)
|
||||||
|
|
||||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, retrievedPrivateKey.size)
|
||||||
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +161,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
|
||||||
|
|
||||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(password, salt, iteration)
|
val retrievedPrivateKey = retrievePrivateKeyWithPassword(password, salt, iteration)
|
||||||
|
|
||||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
assertEquals(EXPECTED_PRIVATE_KEY_LENGTH, retrievedPrivateKey.size)
|
||||||
|
|
||||||
// Data from RiotWeb
|
// Data from RiotWeb
|
||||||
val privateKeyBytes = byteArrayOf(
|
val privateKeyBytes = byteArrayOf(
|
||||||
|
@ -187,5 +179,7 @@ class KeysBackupPasswordTest : InstrumentedTest {
|
||||||
private const val BAD_PASSWORD = "passw0rd"
|
private const val BAD_PASSWORD = "passw0rd"
|
||||||
|
|
||||||
private const val BAD_SALT = "AA0lxhQ9aYgGfMsclVWPIAublg8h9Nlu"
|
private const val BAD_SALT = "AA0lxhQ9aYgGfMsclVWPIAublg8h9Nlu"
|
||||||
|
|
||||||
|
private const val EXPECTED_PRIVATE_KEY_LENGTH = 32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
|
@ -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.network.UserAgentHolder
|
||||||
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
|
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
|
||||||
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
|
import org.matrix.android.sdk.internal.worker.MatrixWorkerFactory
|
||||||
import org.matrix.olm.OlmManager
|
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -59,7 +58,6 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||||
@Inject internal lateinit var debugService: DebugService
|
@Inject internal lateinit var debugService: DebugService
|
||||||
@Inject internal lateinit var userAgentHolder: UserAgentHolder
|
@Inject internal lateinit var userAgentHolder: UserAgentHolder
|
||||||
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
||||||
@Inject internal lateinit var olmManager: OlmManager
|
|
||||||
@Inject internal lateinit var sessionManager: SessionManager
|
@Inject internal lateinit var sessionManager: SessionManager
|
||||||
@Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
|
@Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
|
||||||
@Inject internal lateinit var apiInterceptor: ApiInterceptor
|
@Inject internal lateinit var apiInterceptor: ApiInterceptor
|
||||||
|
|
|
@ -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"
|
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. **/
|
/* 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"
|
const val SSSS_ALGORITHM_AES_HMAC_SHA2 = "m.secret_storage.v1.aes-hmac-sha2"
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -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")
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -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")
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
)
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -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)
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -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")
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
|
@ -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)
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
||||||
import org.matrix.olm.OlmException
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a crypto error response.
|
* Represents a crypto error response.
|
||||||
|
@ -34,8 +33,6 @@ sealed class MXCryptoError : Throwable() {
|
||||||
val detailedErrorDescription: String? = null
|
val detailedErrorDescription: String? = null
|
||||||
) : MXCryptoError()
|
) : MXCryptoError()
|
||||||
|
|
||||||
data class OlmError(val olmException: OlmException) : MXCryptoError()
|
|
||||||
|
|
||||||
data class UnknownDevice(val deviceList: MXUsersDevicesMap<CryptoDeviceInfo>) : MXCryptoError()
|
data class UnknownDevice(val deviceList: MXUsersDevicesMap<CryptoDeviceInfo>) : MXCryptoError()
|
||||||
|
|
||||||
enum class ErrorType {
|
enum class ErrorType {
|
||||||
|
|
|
@ -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.coroutines.builder.safeInvokeOnClose
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.DefaultKeysAlgorithmAndData
|
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.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.network.RequestSender
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.SasVerification
|
import org.matrix.android.sdk.internal.crypto.verification.SasVerification
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.VerificationRequest
|
import org.matrix.android.sdk.internal.crypto.verification.VerificationRequest
|
||||||
|
@ -318,22 +317,6 @@ internal class OlmMachine @Inject constructor(
|
||||||
inner.receiveVerificationEvent(serializedEvent, roomId)
|
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.
|
* Mark the given list of users to be tracked, triggering a key query request for them.
|
||||||
*
|
*
|
||||||
|
|
|
@ -121,7 +121,8 @@ internal class PrepareToEncryptUseCase @Inject constructor(
|
||||||
HistoryVisibility.INVITED
|
HistoryVisibility.INVITED
|
||||||
} else {
|
} else {
|
||||||
HistoryVisibility.JOINED
|
HistoryVisibility.JOINED
|
||||||
}
|
},
|
||||||
|
errorOnVerifiedUserProblem = false,
|
||||||
)
|
)
|
||||||
measureTimeMillis {
|
measureTimeMillis {
|
||||||
keyShareLock.withLock {
|
keyShareLock.withLock {
|
||||||
|
|
|
@ -504,15 +504,8 @@ internal class RustCryptoService @Inject constructor(
|
||||||
val content = event.content?.toModel<EncryptedEventContent>() ?: throw mxCryptoError
|
val content = event.content?.toModel<EncryptedEventContent>() ?: throw mxCryptoError
|
||||||
val roomId = event.roomId
|
val roomId = event.roomId
|
||||||
val sessionId = content.sessionId
|
val sessionId = content.sessionId
|
||||||
val senderKey = content.senderKey
|
|
||||||
if (roomId != null && sessionId != null) {
|
if (roomId != null && sessionId != null) {
|
||||||
// try to perform a lazy migration from legacy store
|
perSessionBackupQueryRateLimiter.tryFromBackupIfPossible(sessionId, roomId)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw mxCryptoError
|
throw mxCryptoError
|
||||||
|
@ -851,9 +844,9 @@ internal class RustCryptoService @Inject constructor(
|
||||||
override fun removeSessionListener(listener: NewSessionListener) {
|
override fun removeSessionListener(listener: NewSessionListener) {
|
||||||
megolmSessionImportManager.removeListener(listener)
|
megolmSessionImportManager.removeListener(listener)
|
||||||
}
|
}
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* DEBUG INFO
|
* DEBUG INFO
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "DefaultCryptoService of $myUserId ($deviceId)"
|
return "DefaultCryptoService of $myUserId ($deviceId)"
|
||||||
|
|
|
@ -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.di.MoshiProvider
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
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.Request
|
||||||
import org.matrix.rustcomponents.sdk.crypto.RequestType
|
import org.matrix.rustcomponents.sdk.crypto.RequestType
|
||||||
import org.matrix.rustcomponents.sdk.crypto.SignatureVerification
|
import org.matrix.rustcomponents.sdk.crypto.SignatureVerification
|
||||||
|
@ -840,8 +839,8 @@ internal class RustKeyBackupService @Inject constructor(
|
||||||
try {
|
try {
|
||||||
olmMachine.enableBackupV1(retrievedMegolmBackupAuthData.publicKey, keysVersionResult.version)
|
olmMachine.enableBackupV1(retrievedMegolmBackupAuthData.publicKey, keysVersionResult.version)
|
||||||
keysBackupVersion = keysVersionResult
|
keysBackupVersion = keysVersionResult
|
||||||
} catch (e: OlmException) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "OlmException")
|
Timber.e(e, "Exception")
|
||||||
keysBackupStateManager.state = KeysBackupState.Disabled
|
keysBackupStateManager.state = KeysBackupState.Disabled
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -20,7 +20,6 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
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_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.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||||
import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService
|
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.SecretShareManager
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.generatePrivateKeyWithPassword
|
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.HkdfSha256
|
||||||
import org.matrix.android.sdk.internal.crypto.tools.withOlmDecryption
|
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.olm.OlmPkMessage
|
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.Mac
|
import javax.crypto.Mac
|
||||||
|
@ -321,22 +318,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||||
?: throw SharedSecretStorageError.ParsingError
|
?: throw SharedSecretStorageError.ParsingError
|
||||||
|
|
||||||
val algorithm = key.keyInfo.content
|
val algorithm = key.keyInfo.content
|
||||||
if (SSSS_ALGORITHM_CURVE25519_AES_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) {
|
|
||||||
// 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) {
|
|
||||||
val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat
|
val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat
|
||||||
return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
|
return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.computation) {
|
||||||
decryptAesHmacSha2(keySpec, name, secretContent)
|
decryptAesHmacSha2(keySpec, name, secretContent)
|
||||||
|
@ -366,8 +348,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor(
|
||||||
val keyInfo = (keyInfoResult as? KeyInfoResult.Success)?.keyInfo
|
val keyInfo = (keyInfoResult as? KeyInfoResult.Success)?.keyInfo
|
||||||
?: return IntegrityResult.Error(SharedSecretStorageError.UnknownKey(keyId ?: ""))
|
?: return IntegrityResult.Error(SharedSecretStorageError.UnknownKey(keyId ?: ""))
|
||||||
|
|
||||||
if (keyInfo.content.algorithm != SSSS_ALGORITHM_AES_HMAC_SHA2 &&
|
if (keyInfo.content.algorithm != SSSS_ALGORITHM_AES_HMAC_SHA2) {
|
||||||
keyInfo.content.algorithm != SSSS_ALGORITHM_CURVE25519_AES_SHA2) {
|
|
||||||
// Unsupported algorithm
|
// Unsupported algorithm
|
||||||
return IntegrityResult.Error(
|
return IntegrityResult.Error(
|
||||||
SharedSecretStorageError.UnsupportedAlgorithm(keyInfo.content.algorithm ?: "")
|
SharedSecretStorageError.UnsupportedAlgorithm(keyInfo.content.algorithm ?: "")
|
||||||
|
|
|
@ -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.crypto.model.DeviceInfo
|
||||||
import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
|
import org.matrix.android.sdk.api.session.events.model.content.EncryptionEventContent
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
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
|
import org.matrix.android.sdk.internal.crypto.store.db.CryptoStoreAggregator
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -143,14 +142,4 @@ interface IMXCommonCryptoStore {
|
||||||
* @return the device or null if not found
|
* @return the device or null if not found
|
||||||
*/
|
*/
|
||||||
fun deviceWithIdentityKey(userId: String, identityKey: String): CryptoDeviceInfo?
|
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?
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.Optional
|
||||||
import org.matrix.android.sdk.api.util.toOptional
|
import org.matrix.android.sdk.api.util.toOptional
|
||||||
import org.matrix.android.sdk.internal.crypto.OlmMachine
|
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.CryptoStoreAggregator
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransaction
|
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.doRealmTransactionAsync
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.doWithRealm
|
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.CryptoRoomInfoMapper
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.mapper.MyDeviceLastSeenInfoEntityMapper
|
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.CryptoMetadataEntity
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntity
|
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.CryptoRoomEntityFields
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntity
|
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.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.getById
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate
|
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.CryptoDatabase
|
||||||
import org.matrix.android.sdk.internal.di.DeviceId
|
import org.matrix.android.sdk.internal.di.DeviceId
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import org.matrix.android.sdk.internal.util.time.Clock
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
@ -75,7 +66,6 @@ private val loggerTag = LoggerTag("RealmCryptoStore", LoggerTag.CRYPTO)
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class RustCryptoStore @Inject constructor(
|
internal class RustCryptoStore @Inject constructor(
|
||||||
@CryptoDatabase private val realmConfiguration: RealmConfiguration,
|
@CryptoDatabase private val realmConfiguration: RealmConfiguration,
|
||||||
private val clock: Clock,
|
|
||||||
@UserId private val userId: String,
|
@UserId private val userId: String,
|
||||||
@DeviceId private val deviceId: String,
|
@DeviceId private val deviceId: String,
|
||||||
private val myDeviceLastSeenInfoEntityMapper: MyDeviceLastSeenInfoEntityMapper,
|
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
|
// Things that should be migrated to another store than realm
|
||||||
// ================================================
|
// ================================================
|
||||||
|
@ -163,30 +139,7 @@ internal class RustCryptoStore @Inject constructor(
|
||||||
// nop
|
// nop
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tidyUpDataBase() {
|
override fun tidyUpDataBase() = Unit
|
||||||
// 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 close() {
|
override fun close() {
|
||||||
val tasks = monarchyWriteAsyncExecutor.shutdownNow()
|
val tasks = monarchyWriteAsyncExecutor.shutdownNow()
|
||||||
|
|
|
@ -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(),
|
|
||||||
)
|
|
|
@ -16,15 +16,9 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto.store.db
|
package org.matrix.android.sdk.internal.crypto.store.db
|
||||||
|
|
||||||
import android.util.Base64
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import io.realm.RealmObject
|
|
||||||
import timber.log.Timber
|
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
|
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.
|
* 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) }
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,31 +17,8 @@
|
||||||
package org.matrix.android.sdk.internal.crypto.store.db
|
package org.matrix.android.sdk.internal.crypto.store.db
|
||||||
|
|
||||||
import io.realm.DynamicRealm
|
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.MigrateCryptoTo024
|
||||||
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.util.database.MatrixRealmMigration
|
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
||||||
import org.matrix.android.sdk.internal.util.time.Clock
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,13 +26,11 @@ import javax.inject.Inject
|
||||||
* 0, 1, 2: legacy Riot-Android;
|
* 0, 1, 2: legacy Riot-Android;
|
||||||
* 3: migrate to RiotX schema;
|
* 3: migrate to RiotX schema;
|
||||||
* 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6).
|
* 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(
|
internal class RealmCryptoStoreMigration @Inject constructor() : MatrixRealmMigration(
|
||||||
private val clock: Clock,
|
|
||||||
private val rustMigrationInfoProvider: RustMigrationInfoProvider,
|
|
||||||
) : MatrixRealmMigration(
|
|
||||||
dbName = "Crypto",
|
dbName = "Crypto",
|
||||||
schemaVersion = 23L,
|
schemaVersion = 24L,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Forces all RealmCryptoStoreMigration instances to be equal.
|
* Forces all RealmCryptoStoreMigration instances to be equal.
|
||||||
|
@ -65,33 +40,6 @@ internal class RealmCryptoStoreMigration @Inject constructor(
|
||||||
override fun hashCode() = 5000
|
override fun hashCode() = 5000
|
||||||
|
|
||||||
override fun doMigrate(realm: DynamicRealm, oldVersion: Long) {
|
override fun doMigrate(realm: DynamicRealm, oldVersion: Long) {
|
||||||
if (oldVersion < 1) MigrateCryptoTo001Legacy(realm).perform()
|
if (oldVersion < 24) MigrateCryptoTo024(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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,23 +17,9 @@
|
||||||
package org.matrix.android.sdk.internal.crypto.store.db
|
package org.matrix.android.sdk.internal.crypto.store.db
|
||||||
|
|
||||||
import io.realm.annotations.RealmModule
|
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.CryptoMetadataEntity
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntity
|
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.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.
|
* Realm module for Crypto store classes.
|
||||||
|
@ -43,21 +29,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEnti
|
||||||
classes = [
|
classes = [
|
||||||
CryptoMetadataEntity::class,
|
CryptoMetadataEntity::class,
|
||||||
CryptoRoomEntity::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,
|
MyDeviceLastSeenInfoEntity::class,
|
||||||
WithHeldSessionEntity::class,
|
|
||||||
SharedSessionEntity::class,
|
|
||||||
OutboundGroupSessionInfoEntity::class
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
internal class RealmCryptoStoreModule
|
internal class RealmCryptoStoreModule
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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...")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,9 +18,6 @@ package org.matrix.android.sdk.internal.crypto.store.db.model
|
||||||
|
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.annotations.PrimaryKey
|
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(
|
internal open class CryptoMetadataEntity(
|
||||||
// The current user id.
|
// The current user id.
|
||||||
|
@ -53,15 +50,4 @@ internal open class CryptoMetadataEntity(
|
||||||
var keyBackupRecoveryKeyVersion: String? = null
|
var keyBackupRecoveryKeyVersion: String? = null
|
||||||
|
|
||||||
// var crossSigningInfoEntity: CrossSigningInfoEntity? = null
|
// var crossSigningInfoEntity: CrossSigningInfoEntity? = null
|
||||||
) : RealmObject() {
|
) : RealmObject()
|
||||||
|
|
||||||
// Deserialize data
|
|
||||||
fun getOlmAccount(): OlmAccount? {
|
|
||||||
return deserializeFromRealm(olmAccountData)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize data
|
|
||||||
fun putOlmAccount(olmAccount: OlmAccount?) {
|
|
||||||
olmAccountData = serializeForRealm(olmAccount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -26,10 +26,6 @@ internal open class CryptoRoomEntity(
|
||||||
var blacklistUnverifiedDevices: Boolean = false,
|
var blacklistUnverifiedDevices: Boolean = false,
|
||||||
// Determines whether or not room history should be shared on new member invites
|
// Determines whether or not room history should be shared on new member invites
|
||||||
var shouldShareHistory: Boolean = false,
|
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
|
// 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
|
// even if a new state event with empty encryption, or state is reset somehow
|
||||||
var wasEncryptedOnce: Boolean? = false,
|
var wasEncryptedOnce: Boolean? = false,
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue