Compare commits

...

357 Commits

Author SHA1 Message Date
valere
8606ac92e1 Fix verify with passphrase test 2023-01-04 09:51:51 +01:00
valere
419673675c Added Self verification UI test 2023-01-03 19:35:15 +01:00
valere
8eda089edc fix rebase 2023-01-03 10:00:02 +01:00
valere
6952d17d16 Merge branch 'develop' into feature/bca/rust_flavor 2023-01-03 09:55:08 +01:00
valere
fab0350ca1 rust key safety integration 2023-01-03 09:53:22 +01:00
valere
54e8debc38 cleaning and logs 2023-01-03 09:52:58 +01:00
valere
76fa1bfee5 Merge branch 'develop' into feature/bca/rust_flavor 2022-12-26 09:29:03 +01:00
valere
695a2b3345 better feedback on verification request sent 2022-12-20 14:58:37 +01:00
valere
ee5dfba389 fix kotlinCrypto flavor compilation 2022-12-17 11:53:13 +01:00
valere
406dfaab85 quick format 2022-12-17 10:52:13 +01:00
valere
d337ccd359 Merge branch 'develop' into feature/bca/rust_flavor 2022-12-17 10:51:49 +01:00
valere
7c888f6334 tmp workaround for big account OOM 2022-12-16 14:03:28 +01:00
valere
2d388f392f ignore verification events from initial sync 2022-12-16 14:03:11 +01:00
valere
ca2d36303c clean logs 2022-12-16 09:34:01 +01:00
valere
3146f5209b Merge branch 'develop' into feature/bca/rust_flavor 2022-12-16 09:33:37 +01:00
valere
f07aa9f6f0 Fix tests and better logs 2022-12-15 18:47:48 +01:00
valere
3efaa8e171 Remove run blocking from realm tx 2022-12-15 18:44:43 +01:00
valere
49239e6bf2 fix ER migration 2022-12-15 18:42:10 +01:00
valere
b224a8d626 Fix lock blocking sync loop 2022-12-15 17:52:14 +01:00
valere
8555b045e7 Fix CI missing key in map 2022-12-14 13:54:48 +01:00
valere
71d56108c2 Fix ui test compilation 2022-12-14 10:43:03 +01:00
valere
8b7238e051 fix CI 2022-12-14 09:47:32 +01:00
valere
ee156239b9 FIx test compilation 2022-12-13 18:56:12 +01:00
valere
f1e8f846b9 fix test compilation 2022-12-13 17:20:47 +01:00
valere
3abd68c153 fix compilation warning 2022-12-13 15:48:13 +01:00
valere
c3f439ea72 code quality 2022-12-13 15:27:13 +01:00
valere
f541be4755 Merge branch 'develop' into feature/bca/rust_flavor 2022-12-13 11:52:46 +01:00
valere
d0807b9239 Fix test compilation 2022-12-13 11:50:06 +01:00
valere
3db82e629b Merge branch 'develop' into feature/bca/rust_flavor 2022-12-13 10:11:21 +01:00
valere
9680b044f9 Fix test crash 2022-12-13 09:48:15 +01:00
valere
c52be1f5b1 Merge branch 'develop' into feature/bca/rust_flavor 2022-12-12 15:26:44 +01:00
valere
8c773b6d00 update crypto crate 2022-12-12 15:07:46 +01:00
valere
438b456f8e quick incremental backup support 2022-12-08 22:53:16 +01:00
valere
4766bc709d Fix: ER showing shields in clear rooms 2022-12-08 18:06:05 +01:00
valere
bfe6207a63 Fix crash when no ER migration needed 2022-12-08 14:44:48 +01:00
valere
2bc0f6c089 use lfs for rust aar lib 2022-12-08 13:54:40 +01:00
valere
14cee226c5 Merge branch 'develop' into feature/bca/rust_flavor 2022-12-08 13:37:43 +01:00
valere
d3ef5cc230 Merge olm to Rust migration 2022-12-08 11:59:46 +01:00
Amit Kumar
17d25e2597
Enable reset all and skip options (#7721)
* Dismiss bottomsheet on skipping verification

* Enable reset all and skip options

* Change ResetAll bottomsheet event to no-op for user verification

* Fix strings and improve state step logic in SharedSecureStorageViewModel
2022-12-07 19:40:22 +05:30
valere
6965c0c5ab move app_name to xml res 2022-12-06 18:11:31 +01:00
valere
cba3c270f5 Reduce room list placeholder lags 2022-12-05 13:47:21 +01:00
valere
0953bc944d Fix test compilation | rust / crypto missing api 2022-12-05 13:46:35 +01:00
valere
adacd55a05 Fix backup authdata serialization 2022-12-05 13:45:58 +01:00
valere
c0614a9fb6 fix CI concurrency 2022-12-04 11:16:25 +01:00
valere
ea37029631 Merge branch 'develop' into feature/bca/rust_flavor 2022-12-04 11:13:46 +01:00
valere
a20fd453d9 fix CI exodus/gplay 2022-12-04 10:34:41 +01:00
valere
03379a6636 Merge branch 'develop' into feature/bca/rust_flavor 2022-12-03 11:15:46 +01:00
valere
ae9711b7d1 Basic sentry e2e reporting for rust + decrypt trust 2022-12-02 18:24:23 +01:00
valere
2ae4b87f2f fix CI 2022-12-02 15:49:32 +01:00
valere
b9045eb25f ktlint 2022-12-02 12:43:05 +01:00
valere
cb4720f6d5 CI update for ER 2022-12-02 12:40:48 +01:00
valere
e6444fe9c0 enable analytics by default on nightly 2022-12-02 12:39:13 +01:00
valere
17b8d3c97b fix unhandled exceptions and cleaning 2022-12-02 12:38:12 +01:00
valere
b0168dc633 fix compilation rust 2022-12-01 16:08:01 +01:00
valere
a110c9ee50 fix moshi number parsing for rust 2022-12-01 16:07:28 +01:00
valere
43421e3eb9 update doc 2022-11-30 19:09:13 +01:00
valere
304989f79c Fix copyrights 2022-11-30 18:15:42 +01:00
valere
9fbc0cdd46 fix compilation 2022-11-30 16:20:02 +01:00
valere
6e371b7d2d code quality 2022-11-30 16:01:11 +01:00
valere
b3d8b1527c cleaning 2022-11-30 15:14:31 +01:00
valere
dd991e759e update workflow 2022-11-30 12:16:07 +01:00
valere
501625c19d post rebase fix rust flavor 2022-11-30 12:10:34 +01:00
valere
a9b970832e Merge branch 'develop' into feature/bca/rust_flavor 2022-11-30 11:05:32 +01:00
valere
bb16d77ec6 fix QR verification 2022-11-30 00:10:46 +01:00
valere
f8d6511c59 rust sas state mapping 2022-11-29 11:22:21 +01:00
valere
fb1995e9c9 only check moshi numbers for rust-sdk 2022-11-29 11:21:54 +01:00
valere
d9342707fd update rust-sdk bindings 2022-11-28 21:48:46 +01:00
Valere
2f3bbab4c4 Fix no deleted device warning on long press menu 2022-11-25 09:04:46 +01:00
Valere
d302fdc655 self verification basics 2022-11-23 11:27:39 +01:00
Valere
5b3e3a7019 Fix sas match action 2022-11-21 18:54:49 +01:00
Valere
4ce6a25c70 refactor for easy unit tests 2022-11-21 15:16:34 +01:00
Valere
bed2c221e3 Fix QR code not always displayed 2022-11-19 22:49:20 +01:00
Valere
0c1e439313 Actor unit test setup 2022-11-19 00:25:08 +01:00
Valere
5c82bdba38 happy path qr kotlin verif 2022-11-17 22:42:47 +01:00
Valere
cf366f7a9c suspend verif WIP 2022-11-16 09:12:54 +01:00
Valere
ae02eb18de Prepare flavors 2022-10-07 14:34:36 +02:00
ganfra
bda031496a
Merge pull request #13 from poljar/fga/feature/handle_request_failing
Fga/feature/handle request failing
2022-06-13 17:16:14 +02:00
ganfra
c0f3f394ac Format ktlint 2022-06-09 11:14:49 +02:00
ganfra
87c1d69e26 Cancel verification flow if request sending fails (after retry) 2022-06-09 10:56:41 +02:00
ganfra
e5ce77de34 Use retry through executeRequest instead of Task.executeRetry 2022-06-09 10:55:59 +02:00
ganfra
52ed7c019b
Merge pull request #12 from poljar/fga/feature/finish_backup_recovery_key
Use backup key directly on olmMachine.
2022-06-08 12:29:43 +02:00
ganfra
6dda30e97f
Merge pull request #11 from poljar/fga/more_fixing_cleaning
Fga/more fixing cleaning
2022-06-08 12:29:18 +02:00
ganfra
b6d73d872b Use backup key directly on olmMachine. 2022-06-07 19:57:44 +02:00
ganfra
abee136867 Ktlint and format 2022-06-06 15:45:06 +02:00
ganfra
327ac2e17b Ignore some failing tests because because using incompatible or unavailable method now. 2022-06-06 15:38:50 +02:00
ganfra
705788394b Fix some more E2EE tests 2022-06-06 14:54:19 +02:00
ganfra
c1961d1fda DeviceId should be non-null so we can inject it properly and OlmMachine too. 2022-06-06 14:53:56 +02:00
ganfra
e519561edf
Merge pull request #9 from poljar/feature/fga/device_verification
Feature/fga/device verification
2022-06-02 15:42:42 +02:00
ganfra
14ed4692dc
Merge branch 'rust' into feature/fga/device_verification 2022-06-02 15:42:25 +02:00
ganfra
022057dd6f
Merge pull request #10 from poljar/feature/fga/backup_recovery_key
Feature/fga/backup recovery key
2022-06-02 15:39:10 +02:00
ganfra
eb0faa3484 Fix some E2E tests 2022-06-02 15:38:28 +02:00
ganfra
47bc597b99 OKHttp: rise maxRequestsPerHost to 64 (instead of default 5) 2022-06-02 15:38:19 +02:00
ganfra
797dc9ccbb Crypto: fix tracking onRoomMembershipEvent 2022-06-02 15:36:06 +02:00
ganfra
c253f6b06f Clean more code 2022-05-31 16:16:56 +02:00
ganfra
3e116ad065 Add RequestDeviceVerification method 2022-05-30 19:55:48 +02:00
ganfra
466260bc6a Fix one test and ignore another one 2022-05-30 19:55:37 +02:00
ganfra
b0aae84727 Use BackupRecoveryKey instead of plain string 2022-05-25 14:45:31 +02:00
ganfra
7c76ba8184 Create BackupRecoveryKey wrapper class (to avoid directly expose uniffi generated classes) 2022-05-25 14:45:16 +02:00
ganfra
b57dfee77e Rename RequestProcessor 2022-05-25 14:44:23 +02:00
ganfra
1809d02541 Temporary fix tests (wait for rust) 2022-05-25 14:42:03 +02:00
ganfra
58a1c80334 Fix crash on on crypto store open/close 2022-05-20 12:47:47 +02:00
ganfra
a6bc730c32
Merge pull request #8 from poljar/feature/fga/rust_upstream_develop
Feature/fga/rust upstream develop
2022-05-20 11:26:34 +02:00
ganfra
21ef138e97 Add more logs on verif 2022-05-20 11:21:51 +02:00
ganfra
42e5dcd50a Use release version 0.1.0 of crypto rust component 2022-05-18 19:01:57 +02:00
ganfra
f559db62b9 Fix tests compilation 2022-05-18 18:48:37 +02:00
ganfra
a559ebad64 Some more cleanup 2022-05-13 18:52:54 +02:00
ganfra
7e49bad411 Try to clean up after merging upstream develop 2022-05-13 17:26:26 +02:00
ganfra
725e56db08 Merge branch 'develop' of https://github.com/vector-im/element-android into feature/fga/rust_upstream_develop 2022-05-12 19:39:10 +02:00
ganfra
559404f953
Merge pull request #7 from poljar/feature/fga/suspend_api
Cleaning up some code and adding more suspend (removing most runBlocking)
2022-05-12 12:06:42 +02:00
ganfra
677c879979 Fix some code quality 2022-05-12 11:59:21 +02:00
ganfra
a2b3839c46 Rust Migration: use realm migration mechanism 2022-05-11 19:20:39 +02:00
ganfra
88733784cd Add tests for extracting and migrate data 2022-05-10 15:17:12 +02:00
ganfra
f9f885418a Introduce TemporaryRealmConfigurationFactory rule 2022-05-10 15:16:46 +02:00
ganfra
4be50101b3 Temporary commenting other tests to make compile 2022-05-10 15:16:21 +02:00
ganfra
43f5fa91d4 Update olm to 3.2.11 2022-05-10 15:15:47 +02:00
ganfra
ae67e51d26 Add RustCryptoStoreMigrateUseCase 2022-05-06 19:15:35 +02:00
ganfra
ff17941cee Make it compile with latest rust lib 2022-05-06 19:14:59 +02:00
ganfra
4e6bed87e4 Introduce ExtractMigrationDataUseCase 2022-05-06 17:51:35 +02:00
ganfra
b4bc56ff5c Continue cleaning up/adding suspend 2022-05-06 16:12:53 +02:00
ganfra
69ede523b6 Update rust-crypto library 2022-05-05 14:35:42 +02:00
ganfra
859d47453c Add local lib in case maven is broken 2022-05-05 11:45:38 +02:00
ganfra
8bd094fa66 Do some cleanup on verification APIs 2022-04-25 18:53:13 +02:00
ganfra
309a290cb8 Suspend: fix flow builders 2022-04-25 17:55:17 +02:00
ganfra
5581b82ab4 Let rust encrypt method handle unencrypted content ( like relates_to) 2022-04-22 12:14:04 +02:00
ganfra
39755b08ee Continue cleaning up code and fix some verification code 2022-04-21 20:09:08 +02:00
ganfra
48793f531c Start fixing crypto tests compilation 2022-04-15 20:05:06 +02:00
ganfra
9cb43ce4c8 Continue cleaning mostly on coroutine 2022-04-15 18:59:09 +02:00
ganfra
ba540eb861 Continue removing runBlocking + some cleanup 2022-04-15 11:17:06 +02:00
ganfra
d020d1f6e0 Use MatrixCoroutineDispatchers in OlmMachine 2022-04-14 16:33:48 +02:00
ganfra
91daa1ab90 Suspend: continue cleaning 2022-04-14 15:36:03 +02:00
ganfra
ed84e38a9b Suspend api: continue moving away from callback 2022-04-06 19:02:45 +02:00
ganfra
9c6fccab1d Suspend API: continue moving verifications 2022-04-01 17:49:44 +02:00
ganfra
950c7f4a23 Fix verification not working 2022-04-01 17:49:08 +02:00
ganfra
046699bc84 Suspend API: handle cross signing service 2022-03-30 17:35:33 +02:00
ganfra
0590258d54 Suspend API: handle verification service 2022-03-29 17:51:05 +02:00
ganfra
e121007d20 Remove rust dependencies and use published aar 2022-03-28 18:15:46 +02:00
ganfra
7436647571
Merge pull request #4 from poljar/rust_upstream_develop
Rust upstream develop
2022-03-10 17:52:10 +01:00
ganfra
dc4569db5a Remove warnings as error for now 2022-03-04 15:51:07 +01:00
ganfra
2f16a2ebd7 Clean up some code 2022-03-04 12:36:31 +01:00
ganfra
b8637ddaf2 Merge branch 'develop' of https://github.com/vector-im/element-android into rust_upstream_develop 2022-03-03 19:52:57 +01:00
Damir Jelić
a194213978 rust: Add the string that failed to be parsed as an user id to the error
If there's an invalid user id that gets passed to the Rust side we're
going to throw an error, this error doesn't tell us what is invalid nor
what the string contained.

Add the string that is being parsed to the error so that the log line
becomes actionable.
2022-02-08 09:43:06 +01:00
Damir Jelić
ee017b7302 rust: Bump the sdk version
This updates the rust-sdk to support fallback keys, note that fallback
keys are not yet uploaded, they just can be used when downloaded and
info about the fallback keys coming from a sync can be passed to the
rust side.
2021-12-09 13:15:54 +01:00
Damir Jelić
5109f10833 Merge branch 'bca/rust_4s_verify_pass' into rust 2021-12-01 11:16:06 +01:00
Valere
14f974f07f Delete old key backup code 2021-12-01 09:12:57 +01:00
Valere
59b2cfa52c Removed useless code 2021-11-30 18:00:26 +01:00
Valere
dc9f6b866b Remove unneeded lax check 2021-11-30 17:50:41 +01:00
Valere
2e71f38f00 quick log improvents 2021-11-30 16:52:11 +01:00
Damir Jelić
71cc38fa78 rust: Bump the rust-sdk version 2021-11-30 16:12:06 +01:00
Valere
8bb2f0584e ktlint clean 2021-11-29 17:48:56 +01:00
Valere
24dc52e4f6 Use ImportKeysResult to notify sessions listeners 2021-11-29 17:48:56 +01:00
Valere
ee6eec041a Fix / unlock keybackup from 4S 2021-11-29 17:48:40 +01:00
Valere
69e4b6e8a4 Improve key decryption perf 2021-11-29 17:48:40 +01:00
Valere
1635c9730a Chunk key import to avoid ram allocation peak 2021-11-29 17:48:40 +01:00
Valere
0e44e32d2a Fix test compilation (not passing) 2021-11-29 17:48:40 +01:00
Valere
210e0241d3 Make keybackup service suspend + fixes 2021-11-29 17:48:40 +01:00
Valere
f0f64d8380 Fix verify with passphrase own device not trusted 2021-11-29 17:48:23 +01:00
Damir Jelić
2167564812 Merge remote-tracking branch 'upstream/develop' into rust 2021-11-29 17:43:40 +01:00
Damir Jelić
3f2755b67c rust: Bump the rust-sdk version 2021-11-29 15:23:44 +01:00
Damir Jelić
d138306b08 rust: Return the map of room keys that were imported 2021-11-25 17:14:02 +01:00
Valere
46ba0eec9f use crypto service in CreateRoomBodyBuilder 2021-11-25 16:57:03 +01:00
Valere
893e6e3962 code review 2021-11-25 16:56:46 +01:00
Valere
fbe098f54b Add all target 2021-11-25 16:04:35 +01:00
Valere
c4bddadebb clean unnecessary safe calls error 2021-11-25 16:03:47 +01:00
Valere
8bebcc93e7 remove some force unwrap 2021-11-25 15:58:59 +01:00
Valere
7fd9ca03be Use new isUserTracked API 2021-11-25 15:42:52 +01:00
Valere
9118b26d2f Fix DM are not e2e by default 2021-11-25 14:36:23 +01:00
Damir Jelić
00280ccb86 rust: Mark room keys that are imported from the backup as backed up 2021-11-25 11:24:47 +01:00
Damir Jelić
3f710ef4c0 rust: Allow individual room keys to be invalid when importing 2021-11-25 10:38:39 +01:00
Damir Jelić
38644f0aa2 crypto: Rewrap and use the new isUserTracked method 2021-11-24 14:42:48 +01:00
Damir Jelić
ac153c80d5 rust: Expose a method to check if a user is tracked 2021-11-24 14:11:56 +01:00
Damir Jelić
87339fb0ee rust: Bump the uniffi version 2021-11-24 14:11:32 +01:00
Valere
a5c500cccd Cleaning + fix copyright 2021-11-19 15:31:23 +01:00
Valere
c01998ddd3 Cleaning 2021-11-19 15:16:29 +01:00
Valere
9e055d9793 post rebase fix 2021-11-19 15:14:04 +01:00
Valere
f209ae26bc Wire compute room shields with rust 2021-11-19 13:29:42 +01:00
Damir Jelić
ae635e2b0a Merge remote-tracking branch 'upstream/develop' into upstream-merge2 2021-11-17 15:35:07 +01:00
Damir Jelić
50cdbaf041 crypto: Update to the latest rust-sdk version 2021-11-17 14:58:14 +01:00
Damir Jelić
a3af73261c crypto: Throw decode errors when creating a recovery key 2021-11-17 13:43:37 +01:00
Damir Jelić
ba7aa3513b crypto: Depend on a hosted rust-sdk version 2021-11-17 13:43:37 +01:00
Damir Jelić
097f05af57 crypto: Throw exceptions when restoring a recovery key from a passphrase 2021-11-17 13:43:37 +01:00
Damir Jelić
e5af7e6109 crypto: Update for the new room key import result 2021-11-17 13:43:37 +01:00
Damir Jelić
7cb143e970 crypto: Don't create a new salt when we just want to rederive a recovery key 2021-11-17 13:43:37 +01:00
Damir Jelić
f1da5a1c7c crypto: Update to the latest ruma 2021-11-17 13:43:37 +01:00
Damir Jelić
50268540c3 crypto: Try to import the recovery key if it was gossiped to us 2021-11-17 13:43:37 +01:00
Damir Jelić
d6ecc7d330 crypto: Connect the backup disabling method 2021-11-17 13:43:37 +01:00
Damir Jelić
5c7b248ed2 crypto: Back up room keys when we create or receive new ones 2021-11-17 13:43:37 +01:00
Damir Jelić
f9476f12af crypto: Correctly continue backing up room keys 2021-11-17 13:43:37 +01:00
Damir Jelić
2b8783b489 crypto: Add support for key backup restoring 2021-11-17 13:43:37 +01:00
Damir Jelić
3b93d6b08c crypto: Fill out all the methods to support backups 2021-11-17 13:43:37 +01:00
Damir Jelić
021041fc2e crypto: Support to send out backup HTTP requests 2021-11-17 13:43:37 +01:00
Damir Jelić
406fd0d8d5 crypto: Initial support for server-side backups of room keys 2021-11-17 13:43:22 +01:00
Damir Jelić
d3a761a73a crypto: Retry the crypto related requests 2021-10-12 12:29:51 +02:00
Damir Jelić
28d4573124 crypto: Use the correct copyright header for the new files 2021-10-12 12:18:02 +02:00
Damir Jelić
c266842da9 crypto: Use getOrPut instead of getOrDefault
getOrDefault won't insert the default value into the map, while we do
want it to be inserted.
2021-10-12 12:08:38 +02:00
Damir Jelić
504fd95b26 crypto: Bump the rust-sdk revision 2021-08-13 20:33:05 +02:00
Damir Jelić
995f1973c7 Merge remote-tracking branch 'upstream/develop' into rust 2021-08-13 13:33:32 +02:00
Damir Jelić
d0502c4f6b crypto: Fix some clippy warnings 2021-08-13 13:30:09 +02:00
Damir Jelić
f1da77fb6b crypto: Avoid converting numbers in to-device requests to floats
This mainly avoid converting the type field in m.olm.v1.curve25519-aes-sha2
variant of an m.room.encrypted event that gets sent out over to-device
messages.
2021-08-13 13:30:09 +02:00
Damir Jelić
00d1233512 crypto: Upload signatures when we confirm a verification as well 2021-08-13 13:30:09 +02:00
Damir Jelić
3365c10fe3 crypto: Add a missing dispatchTxUpdated call to the verifications 2021-08-13 13:30:09 +02:00
Damir Jelić
c85847df57 crypto: Add a Rust based CrossSigningService 2021-08-13 13:30:09 +02:00
Damir Jelić
b012a0ff75 crypto: Export cross signing related methods from the Rust side 2021-08-13 13:30:09 +02:00
Damir Jelić
e2006f9dc6 Merge remote-tracking branch 'upstream/develop' into rust-verification 2021-07-23 15:03:10 +02:00
Damir Jelić
c551b9e0bb crypto: Fill out the docs for the cross signing service 2021-07-23 14:06:03 +02:00
Damir Jelić
2fc691eed2 crypto: Add a method to request verification to the Device class 2021-07-23 11:54:58 +02:00
Damir Jelić
813b48df6a crypto: Document all the new verification methods on the Rust side 2021-07-23 11:50:34 +02:00
Damir Jelić
52dd4bc454 crypto: Document the private methods of the rusty verification service 2021-07-22 13:31:42 +02:00
Damir Jelić
99ff097fc3 crypto: Move the update dispatching logic into a separate class 2021-07-22 12:10:39 +02:00
Damir Jelić
3993d2d4f2 crypto: Remove some redundant methods from the verification service 2021-07-22 11:25:29 +02:00
Damir Jelić
3fa9fc5b7b crypto: Use a background task to fetch user devices 2021-07-21 16:28:12 +02:00
Damir Jelić
cbed5be810 crypto: Move most of the getters of verification objecs into the olm machine 2021-07-21 16:25:28 +02:00
Damir Jelić
38ce3ebed7 crypto: Move the Device class into a separate file 2021-07-21 15:09:21 +02:00
Damir Jelić
8089e972a5 cyrpto: Document the SasVerification class 2021-07-21 14:58:12 +02:00
Damir Jelić
93615ddba9 crypto: Add docs to the VerificationRequest class 2021-07-21 12:11:11 +02:00
Damir Jelić
b500364322 crypto: Expand the docs for the QrCodeVerification class a bit 2021-07-21 12:10:18 +02:00
Damir Jelić
93f36db43c crypto: Add proper scopes to our verification methods 2021-07-20 16:35:50 +02:00
Damir Jelić
2097f4e6c2 crypto: Document the verification methods in the OlmMachine 2021-07-20 16:34:47 +02:00
Damir Jelić
eae2a51a2d crypto: Refactor and document the QR code verification class 2021-07-20 14:30:34 +02:00
Damir Jelić
b33537fd6e crypto: Use the new CancelInfo struct 2021-07-19 14:21:11 +02:00
Damir Jelić
7650e43362 crypto: Add support to scan QR codes during verification 2021-07-10 20:51:47 +02:00
Benoit Marty
b26aba9fc0 Remove EventDecryptor and inject the cryptoService when needed
Not used anymore in RoomSummaryUpdater, to avoid a DI dependency loop. let's see if this is a problem
2021-07-09 12:50:34 +02:00
Benoit Marty
f609bfaf10 This class is not injected. 2021-07-08 18:39:54 +02:00
Benoit Marty
f8ad024f1b Remove some dead code. 2021-07-08 18:38:49 +02:00
Benoit Marty
54c3b4192e Small cleanup and format 2021-07-08 17:14:45 +02:00
Damir Jelić
d4090c4b0a crypto: Only add our own devices if we're requesting devices for our own user 2021-07-08 16:52:31 +02:00
Benoit Marty
80e80e07b3 To be reverted: temporarily change appId to 'im.vector.app.corroded' and app name 2021-07-08 16:18:29 +02:00
Damir Jelić
33c2184c52 crypto: Allow verifications to be requested 2021-07-08 12:49:44 +02:00
Damir Jelić
d24c94d0f9 crypto: Allow the direct start of the short SAS flow 2021-07-01 13:15:26 +02:00
Damir Jelić
85e4b5eb49 Merge remote-tracking branch 'upstream/develop' into rust-verification 2021-07-01 08:09:33 +02:00
Damir Jelić
7e49760da0 rust: Don't depend on a local rust-sdk 2021-06-30 17:39:36 +02:00
Damir Jelić
cd5aad9a31 crypto: Move the request sending logic into the sender 2021-06-30 16:28:21 +02:00
Damir Jelić
2c1dc053ed crypto: Support answering in-room verifications 2021-06-30 15:48:24 +02:00
Damir Jelić
bcfb121215 crypto: Prepare the verification service to allow starting short SAS flows 2021-06-29 11:12:41 +02:00
Damir Jelić
304c89a56d crypto: Dispatch updates when we receive MAC events 2021-06-29 10:03:53 +02:00
Damir Jelić
53b3f54808 crypto: Add support to accept the short SAS verification flow 2021-06-29 09:28:41 +02:00
Damir Jelić
03499b5309 rust: Update the rust-sdk revision. 2021-06-28 15:30:35 +02:00
Damir Jelić
6bb7d5faaa crypto: Dispatch verification request cancellations as well 2021-06-28 15:25:31 +02:00
Damir Jelić
1f7311a428 crypto: Allow verification requests to be canelled 2021-06-28 14:08:49 +02:00
Damir Jelić
05119bcf90 crypto: Allow devices to be marked manually as verified 2021-06-28 14:08:08 +02:00
Damir Jelić
02b8b1f5b1 crypto: Clean up the SAS verification transaction a bit 2021-06-28 11:37:27 +02:00
Damir Jelić
d21137d910 crypto: Add a state for when we confirmed the QR code 2021-06-28 11:37:27 +02:00
Damir Jelić
4473af85b1 crypto: Move more of the request sending logic into the sender class 2021-06-28 11:37:27 +02:00
Damir Jelić
6523ca5afe crypto: Allow the displaying of QR codes 2021-06-28 11:37:27 +02:00
Damir Jelić
d15269a4bd rust: Add methods for the QR code verification 2021-06-28 11:37:27 +02:00
Damir Jelić
846242217b crypto: Move the VerificationRequest into a separate file 2021-06-28 11:37:27 +02:00
Damir Jelić
f95c4ae088 crypto: Allow cancelling of SAS transactions 2021-06-28 11:37:27 +02:00
Damir Jelić
f854e9cf1c crypto: Remove the intermediately CancelCode and use strings to map over FFI 2021-06-28 11:37:27 +02:00
Damir Jelić
b53b0a0093 crypto: Use a when instead of a big if/else statement 2021-06-28 11:37:27 +02:00
Damir Jelić
948aa1a141 crypto: Correctly pick up our device verification state 2021-06-28 11:37:27 +02:00
Damir Jelić
aad18ebec7 crypto: Move the sendToDevice logic to a common class and use it for verifications 2021-06-28 11:37:27 +02:00
Damir Jelić
6a79d022c3 crypto: Expose the trust state of our devices 2021-06-28 11:37:27 +02:00
Damir Jelić
6649aaca2e crypto: Support SAS verification up to showing emojis 2021-06-28 11:37:14 +02:00
Damir Jelić
e97ce33ed9 Merge remote-tracking branch 'upstream/develop' into rust-verification 2021-06-28 11:36:57 +02:00
Damir Jelić
d00b54929f crypto: Add the scaffolding to connect the SAS verification to the rust side 2021-06-20 22:34:54 +02:00
Damir Jelić
0cb9f6be10 rust: Rework the rest of the sas verification methods 2021-06-18 12:16:55 +02:00
Damir Jelić
a4e1a5bbcb crypto: Initial support to answer to-device verification requests 2021-06-17 13:38:30 +02:00
Damir Jelić
e46578a087 rust: Bind the initial verification request type and methods 2021-06-17 13:36:44 +02:00
Damir Jelić
c0bac69733 crypto-service: Use constants when we check the event type 2021-06-16 13:21:37 +02:00
Damir Jelić
5ad596c3bc crypto: Bind more verification methods and types 2021-06-15 13:22:51 +02:00
Damir Jelić
a144b1f7b5 rust: Fix the build and update our deps 2021-06-15 13:16:30 +02:00
Damir Jelić
f110bf34fa Merge remote-tracking branch 'upstream/develop' into rust 2021-06-15 10:26:16 +02:00
Damir Jelić
688c167166 rust: Upgrade our deps 2021-05-25 16:13:12 +02:00
Damir Jelić
49fa34e997 rust: Switch to the new encryption info branch of the rust-sdk 2021-04-29 13:05:08 +02:00
Damir Jelić
fe4abbbeef crypto: Fix a bunch of linter warnings 2021-04-27 16:33:13 +02:00
Damir Jelić
324cdc4db1 Merge remote-tracking branch 'upstream/develop' into rust 2021-04-20 14:44:02 +02:00
Damir Jelić
326641a7e5 crypto: Document the requestRoomKey method 2021-04-20 14:40:58 +02:00
Damir Jelić
711e607fca crypto: Improve the docs a bit 2021-04-20 14:34:54 +02:00
Damir Jelić
389273d56a crypto: Rename the share_group_session method 2021-04-20 14:34:32 +02:00
Damir Jelić
09c0ca10e5 crypto: Enable the sending of outgoing to-device requests 2021-04-20 14:33:43 +02:00
Damir Jelić
ed902fc42a crypto: Improve the startup log line 2021-04-20 14:33:01 +02:00
Damir Jelić
c5173dde71 crypto: Update the rust-sdk branch we're using 2021-04-20 14:32:43 +02:00
Damir Jelić
8bfb7a6e0c crypto: Connect the room key requesting to the rust side 2021-04-20 13:29:52 +02:00
Damir Jelić
0db07011b1 crypto: Return our own device from the store as well
The Kotlin side doesn't differentiate between our own device and other
devices of our own user while the Rust side does, create and return our
own device when it's requested from the store using trusted data.
2021-04-19 19:25:56 +02:00
Damir Jelić
91d28658fc crypto: Correctly decode the byte array when importing keys 2021-04-19 18:04:11 +02:00
Damir Jelić
0afdcb35f1 crypto: Clean up some log lines 2021-04-14 12:46:29 +02:00
Damir Jelić
2805772d0a crypto: Notify the rest of the code about received room keys 2021-04-14 12:16:46 +02:00
Damir Jelić
3ba29b4ea9 crypto: Prepare outgoing to-device requests to be sent out 2021-04-14 12:16:09 +02:00
Damir Jelić
5b761ef7d1 crypto: Document the rust Device struct 2021-04-14 12:15:47 +02:00
Damir Jelić
aebfef8fa9 crypto: Remove a unused method 2021-04-14 12:15:30 +02:00
Damir Jelić
7d67c79d29 crypto: Use the key import progress listener on the rust side 2021-04-14 12:14:52 +02:00
Damir Jelić
543a638e87 crypto: Forward some more errors from the rust side to the kotlin side 2021-04-12 15:03:28 +02:00
Damir Jelić
0d708bc35a rust: Update our deps 2021-04-12 15:02:47 +02:00
Damir Jelić
e9e3d129ba crypto: Send out some of our requests in parallel 2021-04-09 19:10:25 +02:00
Damir Jelić
99477914df crypto: Remove the higher level Device since it's unlikely we'll be using it 2021-04-09 14:47:59 +02:00
Damir Jelić
9296cab4fc crypto: Expose more device data from the rust side 2021-04-09 13:53:55 +02:00
Damir Jelić
188d2d57c0 crypto: Use a concurrent hashmap for the live devices update logic 2021-04-09 13:53:31 +02:00
Damir Jelić
74a1c226a4 crypto: Introduce some locks for some of our e2ee operations 2021-04-09 12:42:22 +02:00
Damir Jelić
8692f05e34 crypto: Connect the room key discarding logic 2021-04-09 12:35:13 +02:00
Damir Jelić
427eb5e249 Merge remote-tracking branch 'upstream/develop' into rust 2021-04-09 09:47:00 +02:00
Damir Jelić
5253f9708c crypto: Fix a crash when we access the devices before the olmMachine is set up
The crypto service is fully initialized only after the first sync but EA
seems to access live devices before that. This results in a crash since
we now use the olm machine to access devices.
2021-04-08 15:55:38 +02:00
Damir Jelić
edfd1b2fe0 crypto: Get rid of the DeviceKeysManager 2021-04-08 15:55:10 +02:00
Damir Jelić
08d0787cc9 crypto: More docs 2021-04-08 11:18:48 +02:00
Damir Jelić
6d05f5b993 crypto: Rename newCrypto to OlmMachine.kt 2021-04-07 15:41:27 +02:00
Damir Jelić
533895cb38 crypto: Document the Rust side of things 2021-04-07 15:04:43 +02:00
Damir Jelić
336697a38c crypto: Some refactoring on the rust side 2021-04-06 15:36:21 +02:00
Damir Jelić
182fc84186 crypto: Split out the live devices observer 2021-04-02 15:30:07 +02:00
Damir Jelić
0b064f647a crypto: Connect the live CryptoDeviceInfo getter methods to the rust-sdk 2021-04-01 17:44:41 +02:00
Damir Jelić
ef93d9e625 crypto: Remove some more unused methods 2021-04-01 15:59:36 +02:00
Damir Jelić
dc8711be30 crypto: Add some TODOs about locking 2021-03-31 11:38:15 +02:00
Damir Jelić
10c7f5b989 crypto: Handle key export decyrption errors 2021-03-30 14:30:39 +02:00
Damir Jelić
6af8041fb4 crypto: Remove the second key export method 2021-03-30 14:30:39 +02:00
Damir Jelić
9d5ef01ce0 crypto: Get devices from the rust-sdk 2021-03-30 14:30:39 +02:00
Damir Jelić
5533c2acae crypto: Remove more unused code 2021-03-30 14:30:39 +02:00
Damir Jelić
57bb723bac crypto: Connect the key importing to the rust-sdk 2021-03-30 14:30:39 +02:00
Damir Jelić
7f89e33037 crypto: Connect the key exporting to the rust-sdk export method 2021-03-29 16:36:40 +02:00
Damir Jelić
32cf645c5f crypto: Remove the myDeviceInfoHolder 2021-03-26 16:01:01 +01:00
Damir Jelić
d49bdbe016 crypto: Remove the RoomDecryptorProvider 2021-03-26 14:02:04 +01:00
Damir Jelić
32c1fd9c85 crypto: Remove the OlmManager 2021-03-26 10:58:58 +01:00
Damir Jelić
1bff219197 crypto: Remove the event decryptor 2021-03-25 16:07:44 +01:00
Damir Jelić
dace959d69 crypto: Get rid of the roomEncryptorsStore 2021-03-25 15:53:18 +01:00
Damir Jelić
3812162f4f crypto: Move the outgoing request sending logic into separate methods 2021-03-25 13:17:06 +01:00
Damir Jelić
4b157f7915 crypto: Don't use the device list manager in onSyncWillProcess 2021-03-24 17:03:36 +01:00
Damir Jelić
4eeb47dc56 crypto: Remove more unused methods 2021-03-24 16:27:38 +01:00
Damir Jelić
6bc825b0bc crypto: Remove the UploadKeysTask copy 2021-03-24 16:15:52 +01:00
Damir Jelić
36451e5410 crypto: Remove the unused olm unwedging method 2021-03-24 16:01:40 +01:00
Damir Jelić
6e53ab2bcf crypto: Remove an unused method 2021-03-24 16:01:19 +01:00
Damir Jelić
629623f720 crypto: Remove the one-time keys uploader 2021-03-24 16:00:51 +01:00
Damir Jelić
555d24fea5 crypto: Remove the delete device with password task 2021-03-24 15:54:56 +01:00
Damir Jelić
1773a361d1 crypto: Remove the method to delete devices with an user password 2021-03-24 15:49:31 +01:00
Damir Jelić
758e8f7fb6 crypto: Remove the cancelRoomKeyRequest method 2021-03-24 15:45:03 +01:00
Damir Jelić
67f238069a crypto: Remove the gossipping managers from the crypto service 2021-03-24 15:44:33 +01:00
Damir Jelić
5b2629ba00 crypto: Remove the incoming gossipping manager 2021-03-24 15:21:39 +01:00
Damir Jelić
981e6b65b0 crypto: Remove the requestRoomKeyForEvent method
This method doesn't seem to be used anywhere, only a single tests seems
to call it. The funcionality has been moved to the rust-sdk and tested
there.
2021-03-24 14:20:39 +01:00
Damir Jelić
515c9be2d9 crypto: Remove the CryptoSyncHandler 2021-03-24 14:17:26 +01:00
Damir Jelić
f5348d6c9d crypto: Remove the object signer from the crypto service 2021-03-24 14:02:32 +01:00
Damir Jelić
e4ac5f6c13 crypto: Don't track users on the kotlin side of things 2021-03-24 13:45:15 +01:00
Damir Jelić
669a5f9815 crypto: Remove the MXOlmDevice 2021-03-24 12:35:21 +01:00
Damir Jelić
7f86f512ed crypto: Remove the one-time key uploader since Rust is handling this 2021-03-24 12:28:41 +01:00
Damir Jelić
ab8d365c10 rust: Build the bindings in release mode 2021-03-15 13:47:51 +01:00
Damir Jelić
c97e384790 crypto: Hook up the event encryption to use the rust-sdk 2021-03-05 16:12:24 +01:00
Damir Jelić
5f848093b9 crypto: Send out to-device requests to share the room key 2021-03-05 13:27:32 +01:00
Damir Jelić
4c44a5e108 crypto: Add support to claim one-time keys 2021-03-04 17:14:48 +01:00
Damir Jelić
da35c9b6bd crypto: Send out key query requests that the rust-sdk gives us. 2021-03-04 13:12:16 +01:00
Damir Jelić
c8c7f23298 rust: Add a README explaining how to build the bindings 2021-02-26 16:40:38 +01:00
Damir Jelić
c828326755 rust: Fix the aarch64 target install dir 2021-02-22 16:04:32 +01:00
Damir Jelić
c33a4710fe ruts: Use the latest master of uniffi. 2021-02-22 16:04:18 +01:00
Damir Jelić
891622d64b crypto: Propagate decryption errors to the kotlin side 2021-02-19 16:33:30 +01:00
Damir Jelić
3b73adf3c5 crypto: Connect the decryption logic to the rust olm machine 2021-02-19 15:42:07 +01:00
Damir Jelić
8b1b771ae6 gitignore: Ignore the generated uniffi sources 2021-02-17 16:21:58 +01:00
Damir Jelić
f6d31f15f1 gradle: Remove support for unsigned integers 2021-02-17 16:21:58 +01:00
Damir Jelić
e2692ec604 crypto: Forward sync crypto chagnes to the rust side 2021-02-17 16:21:58 +01:00
Damir Jelić
01149c8d45 rust: Clean up our deps 2021-02-17 16:21:58 +01:00
Damir Jelić
930e6f4e9b rust: Remove an unused method 2021-02-17 16:21:58 +01:00
Damir Jelić
504e1e31bd rust: Move the logger into a separate module 2021-02-17 16:21:58 +01:00
Damir Jelić
2d620e2ddf rust: Move the errors into a separate module 2021-02-17 16:21:58 +01:00
Damir Jelić
75838fda2a rust: Move the olm machine into a separate module 2021-02-17 16:21:58 +01:00
Damir Jelić
d50df9537c crypto: Connect the rust logger to timber 2021-02-17 16:21:58 +01:00
Damir Jelić
4589b882c0 rust: Add support to forward rust logs to the kotlin side 2021-02-17 16:21:58 +01:00
Damir Jelić
3ddbe7e69b crypto: Use the rust crypto layer to upload device/one-time keys 2021-02-17 16:21:58 +01:00
Damir Jelić
1eeb97ec51 crypto: Expose the new outgoing request method 2021-02-17 16:21:58 +01:00
Damir Jelić
e16c5d07e5 gradle.properties: Increase the heap size since it seems to run out of memory 2021-02-17 16:21:58 +01:00
Damir Jelić
f01e2460e1 crypto: Enable the use of unsigned ints for now and update the bindings wrapper 2021-02-17 16:21:58 +01:00
Damir Jelić
628f530633 rust-sdk: Add a method to get the ougtoing requests 2021-02-17 16:21:57 +01:00
Damir Jelić
0b9be11d85 rust-sdk: Change the sync receiving API to make it a bit more type safe 2021-02-17 16:21:57 +01:00
Damir Jelić
a557c05890 rust-sdk: Add a Makefile to build the bindings and put them into the jni folder 2021-02-17 16:21:57 +01:00
Damir Jelić
5886dc1cbc gitignore: Update the gitignore file for the rust-sdk 2021-02-17 16:21:57 +01:00
Damir Jelić
de5a02b02a matrix-sdk: Initial import of the rust-sdk crypto layer. 2021-02-17 16:21:57 +01:00
400 changed files with 25712 additions and 11923 deletions

1
.gitattributes vendored
View File

@ -1,2 +1,3 @@
**/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text **/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text
**/src/androidTest/assets/*.realm filter=lfs diff=lfs merge=lfs -text **/src/androidTest/assets/*.realm filter=lfs diff=lfs merge=lfs -text
**/matrix-rust-sdk-crypto.aar filter=lfs diff=lfs merge=lfs -text

View File

@ -34,7 +34,7 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-gradle- ${{ runner.os }}-gradle-
- name: Assemble ${{ matrix.target }} debug apk - name: Assemble ${{ matrix.target }} debug apk
run: ./gradlew assemble${{ matrix.target }}Debug $CI_GRADLE_ARG_PROPERTIES run: ./gradlew assemble${{ matrix.target }}KotlinCryptoDebug $CI_GRADLE_ARG_PROPERTIES
- name: Upload ${{ matrix.target }} debug APKs - name: Upload ${{ matrix.target }} debug APKs
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
@ -59,7 +59,7 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-gradle- ${{ runner.os }}-gradle-
- name: Assemble GPlay unsigned apk - name: Assemble GPlay unsigned apk
run: ./gradlew clean assembleGplayRelease $CI_GRADLE_ARG_PROPERTIES run: ./gradlew clean assembleGplayKotlinCryptoRelease $CI_GRADLE_ARG_PROPERTIES
- name: Upload Gplay unsigned APKs - name: Upload Gplay unsigned APKs
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
@ -81,7 +81,7 @@ jobs:
- name: Execute exodus-standalone - name: Execute exodus-standalone
uses: docker://exodusprivacy/exodus-standalone:latest uses: docker://exodusprivacy/exodus-standalone:latest
with: with:
args: /github/workspace/gplay/release/vector-gplay-universal-release-unsigned.apk -j -o /github/workspace/exodus.json args: /github/workspace/gplayKotlinCrypto/release/vector-gplay-kotlinCrypto-universal-release-unsigned.apk -j -o /github/workspace/exodus.json
- name: Upload exodus json report - name: Upload exodus json report
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:

37
.github/workflows/elementr.yml vendored Normal file
View File

@ -0,0 +1,37 @@
name: ER APK Build
on:
pull_request: { }
push:
branches: [ develop ]
# Enrich gradle.properties for CI/CD
env:
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
jobs:
debug:
name: Build debug APKs ER
runs-on: ubuntu-latest
if: github.ref != 'refs/heads/main'
strategy:
fail-fast: false
matrix:
target: [ Gplay, Fdroid ]
# Allow all jobs on develop. Just one per PR.
concurrency:
group: ${{ github.ref == 'refs/heads/develop' && format('elementr-{0}-{1}', matrix.target, github.sha) || format('build-er-debug-{0}-{1}', matrix.target, github.ref) }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Assemble ${{ matrix.target }} debug apk
run: ./gradlew assemble${{ matrix.target }}RustCryptoDebug $CI_GRADLE_ARG_PROPERTIES

View File

@ -38,7 +38,7 @@ jobs:
yes n | towncrier build --version nightly yes n | towncrier build --version nightly
- name: Build and upload Gplay Nightly APK - name: Build and upload Gplay Nightly APK
run: | run: |
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES ./gradlew assembleGplayKotlinCryptoNightly appDistributionUploadGplayKotlinCryptoNightly $CI_GRADLE_ARG_PROPERTIES
env: env:
ELEMENT_ANDROID_NIGHTLY_KEYID: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYID }} ELEMENT_ANDROID_NIGHTLY_KEYID: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYID }}
ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD }} ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD }}

46
.github/workflows/nightly_er.yml vendored Normal file
View File

@ -0,0 +1,46 @@
name: Build and release Element R nightly APK
on:
schedule:
# Every nights at 4
- cron: "0 4 * * *"
env:
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
jobs:
nightly:
name: Build and publish ER nightly Gplay APK to Firebase
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.8
uses: actions/setup-python@v4
with:
python-version: 3.8
- uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Install towncrier
run: |
python3 -m pip install towncrier
- name: Prepare changelog file
run: |
mv towncrier.toml towncrier.toml.bak
sed 's/CHANGES\.md/CHANGES_NIGHTLY\.md/' towncrier.toml.bak > towncrier.toml
rm towncrier.toml.bak
yes n | towncrier build --version nightly
- name: Build and upload Gplay Nightly ER APK
run: |
./gradlew assembleGplayRustCryptoNightly appDistributionUploadGplayRustCryptoNightly $CI_GRADLE_ARG_PROPERTIES
env:
ELEMENT_ANDROID_NIGHTLY_KEYID: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYID }}
ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD }}
ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD }}
FIREBASE_TOKEN: ${{ secrets.ELEMENT_R_NIGHTLY_FIREBASE_TOKEN }}

View File

@ -71,7 +71,7 @@ jobs:
disable-animations: true disable-animations: true
# emulator-build: 7425822 # emulator-build: 7425822
script: | script: |
./gradlew gatherGplayDebugStringTemplates $CI_GRADLE_ARG_PROPERTIES ./gradlew gatherGplayKotlinCryptoDebugStringTemplates $CI_GRADLE_ARG_PROPERTIES
./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES ./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES ./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES ./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES

10
.gitignore vendored
View File

@ -11,6 +11,9 @@
/benchmark-out /benchmark-out
/captures /captures
.externalNativeBuild .externalNativeBuild
rust-sdk/target/*
rust-sdk/src/uniffi/*
Cargo.lock
/tmp /tmp
/fastlane/private /fastlane/private
@ -23,3 +26,10 @@
/yarn.lock /yarn.lock
/node_modules /node_modules
**/out/failures **/out/failures
# For manual dependency to rust crypto sdk
matrix-sdk-android/src/main/jniLibs/
matrix-sdk-android/libs/crypto-android-release.aar
matrix-sdk-android/libs/matrix-rust-sdk-crypto.aar

View File

@ -121,6 +121,15 @@ allprojects {
groups.jcenter.group.each { includeGroup it } groups.jcenter.group.each { includeGroup it }
} }
} }
maven {
url 'https://s01.oss.sonatype.org/content/repositories/snapshots'
content {
groups.mavenSnapshots.regex.each { includeGroupByRegex it }
groups.mavenSnapshots.group.each { includeGroup it }
}
}
} }
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
@ -314,7 +323,7 @@ tasks.register("recordScreenshots", GradleBuild) {
tasks.register("verifyScreenshots", GradleBuild) { tasks.register("verifyScreenshots", GradleBuild) {
startParameter.projectProperties.screenshot = "" startParameter.projectProperties.screenshot = ""
tasks = [':vector:verifyPaparazziDebug'] tasks = [':vector:verifyPaparazziKotlinCryptoDebug']
} }
ext.initScreenshotTests = { project -> ext.initScreenshotTests = { project ->

View File

@ -87,5 +87,5 @@ task unitTestsWithCoverage(type: GradleBuild) {
task instrumentationTestsWithCoverage(type: GradleBuild) { task instrumentationTestsWithCoverage(type: GradleBuild) {
startParameter.projectProperties.coverage = [enableTestCoverage: true] startParameter.projectProperties.coverage = [enableTestCoverage: true]
startParameter.projectProperties['android.testInstrumentationRunnerArguments.notPackage'] = 'im.vector.app.ui' startParameter.projectProperties['android.testInstrumentationRunnerArguments.notPackage'] = 'im.vector.app.ui'
tasks = [':vector-app:connectedGplayDebugAndroidTest', ':vector:connectedDebugAndroidTest', 'matrix-sdk-android:connectedDebugAndroidTest'] tasks = [':vector-app:connectedGplayKotlinCryptoDebugAndroidTest', ':vector:connectedKotlinCryptoDebugAndroidTest', 'matrix-sdk-android:connectedKotlinCryptoDebugAndroidTest']
} }

View File

@ -45,6 +45,13 @@ ext.groups = [
'com.vanniktech', 'com.vanniktech',
] ]
], ],
mavenSnapshots: [
regex: [
],
group: [
'org.matrix.rustcomponents'
]
],
mavenCentral: [ mavenCentral: [
regex: [ regex: [
], ],
@ -205,6 +212,7 @@ ext.groups = [
'org.jvnet.staxex', 'org.jvnet.staxex',
'org.maplibre.gl', 'org.maplibre.gl',
'org.matrix.android', 'org.matrix.android',
'org.matrix.rustcomponents',
'org.mockito', 'org.mockito',
'org.mongodb', 'org.mongodb',
'org.objenesis', 'org.objenesis',

View File

@ -48,7 +48,7 @@ mv towncrier.toml towncrier.toml.bak
sed 's/CHANGES\.md/CHANGES_NIGHTLY\.md/' towncrier.toml.bak > towncrier.toml sed 's/CHANGES\.md/CHANGES_NIGHTLY\.md/' towncrier.toml.bak > towncrier.toml
rm towncrier.toml.bak rm towncrier.toml.bak
yes n | towncrier build --version nightly yes n | towncrier build --version nightly
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES ./gradlew assembleGplayKotlinCryptoNightly appDistributionUploadGplayKotlinCryptoNightly $CI_GRADLE_ARG_PROPERTIES
``` ```
Then you can reset the change on the codebase. Then you can reset the change on the codebase.

20
flavor.gradle Normal file
View File

@ -0,0 +1,20 @@
android {
flavorDimensions "crypto"
productFlavors {
kotlinCrypto {
dimension "crypto"
isDefault = true
// versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}"
// buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"JC\""
// buildConfigField "String", "FLAVOR_DESCRIPTION", "\"KotlinCrypto\""
}
rustCrypto {
dimension "crypto"
// // versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}"
// buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"RC\""
// buildConfigField "String", "FLAVOR_DESCRIPTION", "\"RustCrypto\""
}
}
}

View File

@ -0,0 +1,2 @@
configurations.maybeCreate("default")
artifacts.add("default", file('matrix-rust-sdk-crypto.aar'))

View File

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

View File

@ -414,6 +414,7 @@
<string name="action_play">Play</string> <string name="action_play">Play</string>
<string name="action_dismiss">Dismiss</string> <string name="action_dismiss">Dismiss</string>
<string name="action_reset">Reset</string> <string name="action_reset">Reset</string>
<string name="action_proceed_to_reset">Proceed to reset</string>
<string name="action_learn_more">Learn more</string> <string name="action_learn_more">Learn more</string>
<string name="action_next">Next</string> <string name="action_next">Next</string>
<string name="action_got_it">Got it</string> <string name="action_got_it">Got it</string>
@ -2317,6 +2318,7 @@
<string name="verification_verify_user">Verify %s</string> <string name="verification_verify_user">Verify %s</string>
<string name="verification_verified_user">Verified %s</string> <string name="verification_verified_user">Verified %s</string>
<string name="verification_request_waiting_for">Waiting for %s…</string> <string name="verification_request_waiting_for">Waiting for %s…</string>
<string name="verification_request_waiting_for_recovery">Verifying from Secure Key or Phrase…</string>
<string name="room_profile_not_encrypted_subtitle">Messages in this room are not end-to-end encrypted.</string> <string name="room_profile_not_encrypted_subtitle">Messages in this room are not end-to-end encrypted.</string>
<string name="direct_room_profile_not_encrypted_subtitle">Messages here are not end-to-end encrypted.</string> <string name="direct_room_profile_not_encrypted_subtitle">Messages here are not end-to-end encrypted.</string>
<string name="room_profile_encrypted_subtitle">Messages in this room are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them.</string> <string name="room_profile_encrypted_subtitle">Messages in this room are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them.</string>
@ -2426,6 +2428,8 @@
<string name="crosssigning_cannot_verify_this_session">Unable to verify this device</string> <string name="crosssigning_cannot_verify_this_session">Unable to verify this device</string>
<string name="crosssigning_cannot_verify_this_session_desc">You wont be able to access encrypted message history. Reset your Secure Message Backup and verification keys to start fresh.</string> <string name="crosssigning_cannot_verify_this_session_desc">You wont be able to access encrypted message history. Reset your Secure Message Backup and verification keys to start fresh.</string>
<string name="verification_verify_with_another_device">Verify with another device</string>
<string name="verification_verify_identity">Verify your identity to access encrypted messages and prove your identity to others.</string>
<string name="verification_open_other_to_verify">Use an existing session to verify this one, granting it access to encrypted messages.</string> <string name="verification_open_other_to_verify">Use an existing session to verify this one, granting it access to encrypted messages.</string>
<string name="verification_profile_verify">Verify</string> <string name="verification_profile_verify">Verify</string>
@ -2500,6 +2504,7 @@
<string name="new_session">New login. Was this you?</string> <string name="new_session">New login. Was this you?</string>
<string name="verify_new_session_notice">Use this session to verify your new one, granting it access to encrypted messages.</string> <string name="verify_new_session_notice">Use this session to verify your new one, granting it access to encrypted messages.</string>
<string name="verification_request_was_sent">A verification request has been sent. Open one of your other sessions to accept and start the verification.</string>
<string name="verify_new_session_was_not_me">This wasnt me</string> <string name="verify_new_session_was_not_me">This wasnt me</string>
<string name="verify_new_session_compromized">Your account may be compromised</string> <string name="verify_new_session_compromized">Your account may be compromised</string>
@ -2639,8 +2644,10 @@
<string name="bad_passphrase_key_reset_all_action">Forgot or lost all recovery options? Reset everything</string> <string name="bad_passphrase_key_reset_all_action">Forgot or lost all recovery options? Reset everything</string>
<string name="secure_backup_reset_all">Reset everything</string> <string name="secure_backup_reset_all">Reset everything</string>
<string name="secure_backup_reset_all_no_other_devices">Only do this if you have no other device you can verify this device with.</string> <string name="secure_backup_reset_all_no_other_devices">Only do this if you have no other device you can verify this device with.</string>
<string name="secure_backup_reset_all_no_other_devices_long">Resetting your verification keys cannot be undone. After resetting, you won\'t have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.</string>
<string name="secure_backup_reset_if_you_reset_all">If you reset everything</string> <string name="secure_backup_reset_if_you_reset_all">If you reset everything</string>
<string name="secure_backup_reset_no_history">You will restart with no history, no messages, trusted devices or trusted users</string> <string name="secure_backup_reset_no_history">You will restart with no history, no messages, trusted devices or trusted users</string>
<string name="secure_backup_reset_danger_warning">Please only proceed if you\'re sure you\'ve lost all of your other devices and your security key.</string>
<plurals name="secure_backup_reset_devices_you_can_verify"> <plurals name="secure_backup_reset_devices_you_can_verify">
<item quantity="one">Show the device you can verify with now</item> <item quantity="one">Show the device you can verify with now</item>
<item quantity="other">Show %d devices you can verify with now</item> <item quantity="other">Show %d devices you can verify with now</item>
@ -2655,6 +2662,7 @@
<string name="unencrypted">Unencrypted</string> <string name="unencrypted">Unencrypted</string>
<string name="encrypted_unverified">Encrypted by an unverified device</string> <string name="encrypted_unverified">Encrypted by an unverified device</string>
<string name="encrypted_by_deleted">Encrypted by a deleted device</string>
<string name="key_authenticity_not_guaranteed">The authenticity of this encrypted message can\'t be guaranteed on this device.</string> <string name="key_authenticity_not_guaranteed">The authenticity of this encrypted message can\'t be guaranteed on this device.</string>
<!-- TODO TO BE REMOVED --> <!-- TODO TO BE REMOVED -->
<string name="review_logins" tools:ignore="UnusedResources">Review where youre logged in</string> <string name="review_logins" tools:ignore="UnusedResources">Review where youre logged in</string>

View File

@ -3,6 +3,7 @@ plugins {
id 'com.android.library' id 'com.android.library'
id 'org.jetbrains.kotlin.android' id 'org.jetbrains.kotlin.android'
} }
apply from: '../flavor.gradle'
android { android {
namespace "org.matrix.android.sdk.flow" namespace "org.matrix.android.sdk.flow"
@ -30,11 +31,23 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "11"
} }
// publishNonDefault true
} }
//configurations {
// kotlinCryptoDebugImplementation
// kotlinCryptoReleaseImplementation
// rustCryptoDebugImplementation
// rustCryptoReleaseImplementation
//}
dependencies { dependencies {
implementation project(":matrix-sdk-android") implementation project(":matrix-sdk-android")
// kotlinCryptoDebugImplementation project(path: ":matrix-sdk-android", configuration :"kotlinCryptoDebug")
// kotlinCryptoReleaseImplementation project(path: ":matrix-sdk-android", configuration :"kotlinCryptoRelease")
// rustCryptoDebugImplementation project(path: ":matrix-sdk-android", configuration :"rustCryptoDebug")
// rustCryptoReleaseImplementation project(path: ":matrix-sdk-android", configuration :"rustCryptoDebug")
implementation libs.jetbrains.coroutinesCore implementation libs.jetbrains.coroutinesCore
implementation libs.jetbrains.coroutinesAndroid implementation libs.jetbrains.coroutinesAndroid
implementation libs.androidx.lifecycleLivedata implementation libs.androidx.lifecycleLivedata

View File

@ -41,6 +41,7 @@ dokkaHtml {
} }
} }
} }
apply from: '../flavor.gradle'
android { android {
namespace "org.matrix.android.sdk" namespace "org.matrix.android.sdk"
@ -75,7 +76,7 @@ android {
testOptions { testOptions {
// Comment to run on Android 12 // Comment to run on Android 12
// execution 'ANDROIDX_TEST_ORCHESTRATOR' execution 'ANDROIDX_TEST_ORCHESTRATOR'
} }
buildTypes { buildTypes {
@ -124,6 +125,7 @@ android {
java.srcDirs += "src/sharedTest/java" java.srcDirs += "src/sharedTest/java"
} }
} }
} }
static def gitRevision() { static def gitRevision() {
@ -141,12 +143,23 @@ static def gitRevisionDate() {
return cmd.execute().text.trim() return cmd.execute().text.trim()
} }
configurations.all {
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}
dependencies { dependencies {
implementation libs.jetbrains.coroutinesCore implementation libs.jetbrains.coroutinesCore
implementation libs.jetbrains.coroutinesAndroid implementation libs.jetbrains.coroutinesAndroid
// implementation(name: 'crypto-android-release', ext: 'aar')
implementation 'net.java.dev.jna:jna:5.10.0@aar'
// implementation libs.androidx.appCompat
implementation libs.androidx.core implementation libs.androidx.core
rustCryptoImplementation libs.androidx.lifecycleLivedata
// Lifecycle // Lifecycle
implementation libs.androidx.lifecycleCommon implementation libs.androidx.lifecycleCommon
implementation libs.androidx.lifecycleProcess implementation libs.androidx.lifecycleProcess
@ -203,6 +216,10 @@ dependencies {
implementation libs.google.phonenumber implementation libs.google.phonenumber
// rustCryptoImplementation 'org.matrix.rustcomponents:crypto-android:0.2.1-SNAPSHOT'
// rustCryptoImplementation files('libs/matrix-rust-sdk-crypto.aar')
rustCryptoApi project(":library:rustCrypto")
testImplementation libs.tests.junit testImplementation libs.tests.junit
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
testImplementation libs.mockk.mockk testImplementation libs.mockk.mockk

View File

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

View File

@ -19,13 +19,12 @@ package org.matrix.android.sdk
import android.content.Context import android.content.Context
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import org.junit.Rule import org.junit.Rule
import org.matrix.android.sdk.common.RetryTestRule
import org.matrix.android.sdk.test.shared.createTimberTestRule import org.matrix.android.sdk.test.shared.createTimberTestRule
interface InstrumentedTest { interface InstrumentedTest {
@Rule // @Rule
fun retryTestRule() = RetryTestRule(3) // fun retryTestRule() = RetryTestRule(3)
@Rule @Rule
fun timberTestRule() = createTimberTestRule() fun timberTestRule() = createTimberTestRule()

View File

@ -85,13 +85,15 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig:
internal fun runCryptoTest(context: Context, cryptoConfig: MXCryptoConfig? = null, autoSignoutOnClose: Boolean = true, block: suspend CoroutineScope.(CryptoTestHelper, CommonTestHelper) -> Unit) { internal fun runCryptoTest(context: Context, cryptoConfig: MXCryptoConfig? = null, autoSignoutOnClose: Boolean = true, block: suspend CoroutineScope.(CryptoTestHelper, CommonTestHelper) -> Unit) {
val testHelper = CommonTestHelper(context, cryptoConfig) val testHelper = CommonTestHelper(context, cryptoConfig)
val cryptoTestHelper = CryptoTestHelper(testHelper) val cryptoTestHelper = CryptoTestHelper(testHelper)
return runTest(dispatchTimeoutMs = TestConstants.timeOutMillis) { return try {
try { runTest(dispatchTimeoutMs = TestConstants.timeOutMillis * 2) {
withContext(Dispatchers.Default) { withContext(Dispatchers.Main) {
block(cryptoTestHelper, testHelper) block(cryptoTestHelper, testHelper)
} }
}
} finally { } finally {
if (autoSignoutOnClose) { if (autoSignoutOnClose) {
runBlocking {
testHelper.cleanUpOpenedSessions() testHelper.cleanUpOpenedSessions()
} }
} }
@ -250,7 +252,7 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig:
// not sure why it's taking so long :/ // not sure why it's taking so long :/
wrapWithTimeout(90_000) { wrapWithTimeout(90_000) {
Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $roomID") Log.v("#E2E TEST", "${otherSession.myUserId.take(10)} tries to join room $roomID")
try { try {
otherSession.roomService().joinRoom(roomID) otherSession.roomService().joinRoom(roomID)
} catch (ex: JoinRoomFailure.JoinedWithTimeout) { } catch (ex: JoinRoomFailure.JoinedWithTimeout) {
@ -432,6 +434,31 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig:
} }
} }
private val backoff = listOf(500L, 1_000L, 1_000L, 3_000L, 3_000L, 5_000L)
suspend fun retryWithBackoff(
timeout: Long = TestConstants.timeOutMillis,
// we use on fail to let caller report a proper error that will show nicely in junit test result with correct line
// just call fail with your message
onFail: (() -> Unit)? = null,
predicate: suspend () -> Boolean,
) {
var backoffTry = 0
val now = System.currentTimeMillis()
while (!predicate()) {
Timber.v("## retryWithBackoff Trial nb $backoffTry")
withContext(Dispatchers.IO) {
delay(backoff[backoffTry.coerceAtMost(backoff.size - 1)])
}
backoffTry++
if (System.currentTimeMillis() - now > timeout) {
Timber.v("## retryWithBackoff Trial fail")
onFail?.invoke()
return
}
}
Timber.w("## VALR Trial success for")
}
suspend fun <T> waitForCallback(timeout: Long = TestConstants.timeOutMillis, block: (MatrixCallback<T>) -> Unit): T { suspend fun <T> waitForCallback(timeout: Long = TestConstants.timeOutMillis, block: (MatrixCallback<T>) -> Unit): T {
return wrapWithTimeout(timeout) { return wrapWithTimeout(timeout) {
suspendCoroutine { continuation -> suspendCoroutine { continuation ->

View File

@ -37,4 +37,10 @@ data class CryptoTestData(
testHelper.signOutAndClose(it) testHelper.signOutAndClose(it)
} }
} }
suspend fun initializeCrossSigning(testHelper: CryptoTestHelper) {
sessions.forEach {
testHelper.initializeCrossSigning(it)
}
}
} }

View File

@ -33,18 +33,18 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupUtils
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
@ -52,7 +52,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner
import org.matrix.android.sdk.api.session.securestorage.KeyRef import org.matrix.android.sdk.api.session.securestorage.KeyRef
import org.matrix.android.sdk.api.util.toBase64NoPadding import timber.log.Timber
import java.util.UUID import java.util.UUID
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -121,6 +121,80 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
return CryptoTestData(aliceRoomId, listOf(aliceSession, bobSession)) return CryptoTestData(aliceRoomId, listOf(aliceSession, bobSession))
} }
suspend fun inviteNewUsersAndWaitForThemToJoin(session: Session, roomId: String, usernames: List<String>): List<Session> {
val newSessions = usernames.map { username ->
testHelper.createAccount(username, SessionTestParams(true)).also {
it.cryptoService().enableKeyGossiping(false)
}
}
val room = session.getRoom(roomId)!!
Log.v("#E2E TEST", "accounts for ${usernames.joinToString(",") { it.take(10) }} created")
// we want to invite them in the room
newSessions.forEach { newSession ->
Log.v("#E2E TEST", "${session.myUserId.take(10)} invites ${newSession.myUserId.take(10)}")
room.membershipService().invite(newSession.myUserId)
}
// All user should accept invite
newSessions.forEach { newSession ->
waitForAndAcceptInviteInRoom(newSession, roomId)
Log.v("#E2E TEST", "${newSession.myUserId.take(10)} joined room $roomId")
}
ensureMembersHaveJoined(session, newSessions, roomId)
return newSessions
}
private suspend fun ensureMembersHaveJoined(session: Session, invitedUserSessions: List<Session>, roomId: String) {
testHelper.retryWithBackoff(
onFail = {
fail("Members ${invitedUserSessions.map { it.myUserId.take(10) }} should have join from the pov of ${session.myUserId.take(10)}")
}
) {
invitedUserSessions.map { invitedUserSession ->
session.roomService().getRoomMember(invitedUserSession.myUserId, roomId)?.membership?.also {
Log.v("#E2E TEST", "${invitedUserSession.myUserId.take(10)} membership is $it")
}
}.all {
it == Membership.JOIN
}
}
}
private suspend fun waitForAndAcceptInviteInRoom(session: Session, roomId: String) {
testHelper.retryWithBackoff(
onFail = {
fail("${session.myUserId} cannot see the invite from ${session.myUserId.take(10)}")
}
) {
val roomSummary = session.getRoomSummary(roomId)
(roomSummary != null && roomSummary.membership == Membership.INVITE).also {
if (it) {
Log.v("#E2E TEST", "${session.myUserId.take(10)} can see the invite from ${roomSummary?.inviterId}")
}
}
}
// not sure why it's taking so long :/
Log.v("#E2E TEST", "${session.myUserId.take(10)} tries to join room $roomId")
try {
session.roomService().joinRoom(roomId)
} catch (ex: JoinRoomFailure.JoinedWithTimeout) {
// it's ok we will wait after
}
Log.v("#E2E TEST", "${session.myUserId} waiting for join echo ...")
testHelper.retryWithBackoff(
onFail = {
fail("${session.myUserId.take(10)} cannot see the join echo for ${roomId}")
}
) {
val roomSummary = session.getRoomSummary(roomId)
roomSummary != null && roomSummary.membership == Membership.JOIN
}
}
/** /**
* @return Alice and Bob sessions * @return Alice and Bob sessions
*/ */
@ -189,7 +263,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
return MegolmBackupCreationInfo( return MegolmBackupCreationInfo(
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
authData = createFakeMegolmBackupAuthData(), authData = createFakeMegolmBackupAuthData(),
recoveryKey = "fake" recoveryKey = BackupUtils.recoveryKeyFromPassphrase("3cnTdW")!!
) )
} }
@ -221,7 +295,6 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
} }
suspend fun initializeCrossSigning(session: Session) { suspend fun initializeCrossSigning(session: Session) {
testHelper.waitForCallback<Unit> {
session.cryptoService().crossSigningService() session.cryptoService().crossSigningService()
.initializeCrossSigning( .initializeCrossSigning(
object : UserInteractiveAuthInterceptor { object : UserInteractiveAuthInterceptor {
@ -234,9 +307,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
) )
) )
} }
}, it })
)
}
} }
/** /**
@ -272,16 +343,13 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
) )
// set up megolm backup // set up megolm backup
val creationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> { val creationInfo = session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null)
session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it) val version = session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo)
}
val version = testHelper.waitForCallback<KeysVersion> {
session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
}
// Save it for gossiping // Save it for gossiping
session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version) session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret -> creationInfo.recoveryKey.toBase64().let { secret ->
ssssService.storeSecret( ssssService.storeSecret(
KEYBACKUP_SECRET_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME,
secret, secret,
@ -298,61 +366,78 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
val bobVerificationService = bob.cryptoService().verificationService() val bobVerificationService = bob.cryptoService().verificationService()
val localId = UUID.randomUUID().toString() val localId = UUID.randomUUID().toString()
aliceVerificationService.requestKeyVerificationInDMs( val requestID = aliceVerificationService.requestKeyVerificationInDMs(
localId = localId, localId = localId,
methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
otherUserId = bob.myUserId, otherUserId = bob.myUserId,
roomId = roomId roomId = roomId
).transactionId ).transactionId
testHelper.retryPeriodically { testHelper.retryWithBackoff(
onFail = {
fail("Bob should see an incoming request from alice")
}
) {
bobVerificationService.getExistingVerificationRequests(alice.myUserId).firstOrNull { bobVerificationService.getExistingVerificationRequests(alice.myUserId).firstOrNull {
it.requestInfo?.fromDevice == alice.sessionParams.deviceId it.otherDeviceId == alice.sessionParams.deviceId
} != null } != null
} }
val incomingRequest = bobVerificationService.getExistingVerificationRequests(alice.myUserId).first { val incomingRequest = bobVerificationService.getExistingVerificationRequests(alice.myUserId).first {
it.requestInfo?.fromDevice == alice.sessionParams.deviceId it.otherDeviceId == alice.sessionParams.deviceId
} }
bobVerificationService.readyPendingVerificationInDMs(listOf(VerificationMethod.SAS), alice.myUserId, roomId, incomingRequest.transactionId!!)
var requestID: String? = null Timber.v("#TEST Incoming request is $incomingRequest")
Timber.v("#TEST let bob ready the verification with SAS method")
bobVerificationService.readyPendingVerification(
listOf(VerificationMethod.SAS),
alice.myUserId,
incomingRequest.transactionId
)
// wait for it to be readied // wait for it to be readied
testHelper.retryPeriodically { testHelper.retryWithBackoff(
val outgoingRequest = aliceVerificationService.getExistingVerificationRequests(bob.myUserId) onFail = {
.firstOrNull { it.localId == localId } fail("Alice should see the verification in ready state")
if (outgoingRequest?.isReady == true) {
requestID = outgoingRequest.transactionId!!
true
} else {
false
} }
) {
val outgoingRequest = aliceVerificationService.getExistingVerificationRequest(bob.myUserId, requestID)
outgoingRequest?.state == EVerificationState.Ready
} }
aliceVerificationService.beginKeyVerificationInDMs( Timber.v("#TEST let alice start the verification")
aliceVerificationService.startKeyVerification(
VerificationMethod.SAS, VerificationMethod.SAS,
requestID!!,
roomId,
bob.myUserId, bob.myUserId,
bob.sessionParams.credentials.deviceId!! requestID,
) )
// we should reach SHOW SAS on both // we should reach SHOW SAS on both
var alicePovTx: OutgoingSasVerificationTransaction? = null var alicePovTx: SasVerificationTransaction? = null
var bobPovTx: IncomingSasVerificationTransaction? = null var bobPovTx: SasVerificationTransaction? = null
testHelper.retryPeriodically { testHelper.retryWithBackoff(
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID!!) as? OutgoingSasVerificationTransaction onFail = {
Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}") fail("Alice should should see a verification code")
alicePovTx?.state == VerificationTxState.ShortCodeReady }
) {
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID)
as? SasVerificationTransaction
Log.v("TEST", "== alicePovTx id:${requestID} is ${alicePovTx?.state()}")
alicePovTx?.getDecimalCodeRepresentation() != null
} }
// wait for alice to get the ready // wait for alice to get the ready
testHelper.retryPeriodically { testHelper.retryWithBackoff(
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID!!) as? IncomingSasVerificationTransaction onFail = {
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}") fail("Bob should should see a verification code")
if (bobPovTx?.state == VerificationTxState.OnStarted) {
bobPovTx?.performAccept()
} }
bobPovTx?.state == VerificationTxState.ShortCodeReady ) {
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID)
as? SasVerificationTransaction
Log.v("TEST", "== bobPovTx is ${bobPovTx?.state()}")
// bobPovTx?.state == VerificationTxState.ShortCodeReady
bobPovTx?.getDecimalCodeRepresentation() != null
} }
assertEquals("SAS code do not match", alicePovTx!!.getDecimalCodeRepresentation(), bobPovTx!!.getDecimalCodeRepresentation()) assertEquals("SAS code do not match", alicePovTx!!.getDecimalCodeRepresentation(), bobPovTx!!.getDecimalCodeRepresentation())
@ -360,11 +445,11 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
bobPovTx!!.userHasVerifiedShortCode() bobPovTx!!.userHasVerifiedShortCode()
alicePovTx!!.userHasVerifiedShortCode() alicePovTx!!.userHasVerifiedShortCode()
testHelper.retryPeriodically { testHelper.retryWithBackoff {
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId) alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
} }
testHelper.retryPeriodically { testHelper.retryWithBackoff {
bob.cryptoService().crossSigningService().isUserTrusted(alice.myUserId) bob.cryptoService().crossSigningService().isUserTrusted(alice.myUserId)
} }
} }

View File

@ -46,7 +46,7 @@ class DecryptRedactedEventTest : InstrumentedTest {
roomALicePOV.sendService().redactEvent(timelineEvent.root, redactionReason) roomALicePOV.sendService().redactEvent(timelineEvent.root, redactionReason)
// get the event from bob // get the event from bob
testHelper.retryPeriodically { testHelper.retryWithBackoff {
bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)?.root?.isRedacted() == true bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)?.root?.isRedacted() == true
} }

View File

@ -27,10 +27,6 @@ import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
@ -217,15 +213,11 @@ class E2EShareKeysConfigTest : InstrumentedTest {
Log.v("#E2E TEST", "Create and start key backup for bob ...") Log.v("#E2E TEST", "Create and start key backup for bob ...")
val keysBackupService = aliceSession.cryptoService().keysBackupService() val keysBackupService = aliceSession.cryptoService().keysBackupService()
val keyBackupPassword = "FooBarBaz" val keyBackupPassword = "FooBarBaz"
val megolmBackupCreationInfo = commonTestHelper.waitForCallback<MegolmBackupCreationInfo> { val megolmBackupCreationInfo = keysBackupService.prepareKeysBackupVersion(keyBackupPassword, null)
keysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it) val version = keysBackupService.createKeysBackupVersion(megolmBackupCreationInfo)
}
val version = commonTestHelper.waitForCallback<KeysVersion> {
keysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it)
}
commonTestHelper.waitForCallback<Unit> { commonTestHelper.retryPeriodically {
keysBackupService.backupAllGroupSessions(null, it) keysBackupService.getTotalNumbersOfKeys() == keysBackupService.getTotalNumbersOfBackedUpKeys()
} }
// signout // signout
@ -235,20 +227,15 @@ class E2EShareKeysConfigTest : InstrumentedTest {
newAliceSession.cryptoService().enableShareKeyOnInvite(true) newAliceSession.cryptoService().enableShareKeyOnInvite(true)
newAliceSession.cryptoService().keysBackupService().let { kbs -> newAliceSession.cryptoService().keysBackupService().let { kbs ->
val keyVersionResult = commonTestHelper.waitForCallback<KeysVersionResult?> { val keyVersionResult = kbs.getVersion(version.version)
kbs.getVersion(version.version, it)
}
val importedResult = commonTestHelper.waitForCallback<ImportRoomKeysResult> { val importedResult = kbs.restoreKeyBackupWithPassword(
kbs.restoreKeyBackupWithPassword(
keyVersionResult!!, keyVersionResult!!,
keyBackupPassword, keyBackupPassword,
null, null,
null, null,
null, null,
it
) )
}
assertEquals(2, importedResult.totalNumberOfKeys) assertEquals(2, importedResult.totalNumberOfKeys)
} }

View File

@ -18,12 +18,7 @@ package org.matrix.android.sdk.internal.crypto
import android.util.Log import android.util.Log
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.suspendCancellableCoroutine
import org.amshove.kluent.fail import org.amshove.kluent.fail
import org.amshove.kluent.internal.assertEquals import org.amshove.kluent.internal.assertEquals
import org.junit.Assert import org.junit.Assert
@ -40,17 +35,6 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
@ -92,7 +76,6 @@ class E2eeSanityTests : InstrumentedTest {
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val e2eRoomID = cryptoTestData.roomId val e2eRoomID = cryptoTestData.roomId
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!! val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
// we want to disable key gossiping to just check initial sending of keys // we want to disable key gossiping to just check initial sending of keys
aliceSession.cryptoService().enableKeyGossiping(false) aliceSession.cryptoService().enableKeyGossiping(false)
@ -100,70 +83,50 @@ class E2eeSanityTests : InstrumentedTest {
// add some more users and invite them // add some more users and invite them
val otherAccounts = listOf("benoit", "valere", "ganfra") // , "adam", "manu") val otherAccounts = listOf("benoit", "valere", "ganfra") // , "adam", "manu")
.map { .let {
testHelper.createAccount(it, SessionTestParams(true)).also { cryptoTestHelper.inviteNewUsersAndWaitForThemToJoin(aliceSession, e2eRoomID, it)
it.cryptoService().enableKeyGossiping(false)
} }
}
Log.v("#E2E TEST", "All accounts created")
// we want to invite them in the room
otherAccounts.forEach {
Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
aliceRoomPOV.membershipService().invite(it.myUserId)
}
// All user should accept invite
otherAccounts.forEach { otherSession ->
testHelper.waitForAndAcceptInviteInRoom(otherSession, e2eRoomID)
Log.v("#E2E TEST", "${otherSession.myUserId} joined room $e2eRoomID")
}
// check that alice see them as joined (not really necessary?)
ensureMembersHaveJoined(testHelper, aliceSession, otherAccounts, e2eRoomID)
Log.v("#E2E TEST", "All users have joined the room") Log.v("#E2E TEST", "All users have joined the room")
Log.v("#E2E TEST", "Alice is sending the message") Log.v("#E2E TEST", "Alice is sending the message")
val text = "This is my message" val text = "This is my message"
val sentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, text) val sentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, text)
// val sentEvent = testHelper.sendTextMessage(aliceRoomPOV, "Hello all", 1).first()
Assert.assertTrue("Message should be sent", sentEventId != null) Assert.assertTrue("Message should be sent", sentEventId != null)
// All should be able to decrypt // All should be able to decrypt
otherAccounts.forEach { otherSession -> otherAccounts.forEach { otherSession ->
testHelper.retryPeriodically { testHelper.retryWithBackoff(
val timeLineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!) onFail = {
fail("${otherSession.myUserId.take(10)} should be able to decrypt")
}) {
val timeLineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also {
Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}|${it?.root?.mxDecryptionResult?.isSafe}")
}
timeLineEvent != null && timeLineEvent != null &&
timeLineEvent.isEncrypted() && timeLineEvent.isEncrypted() &&
timeLineEvent.root.getClearType() == EventType.MESSAGE && timeLineEvent.root.getClearType() == EventType.MESSAGE &&
timeLineEvent.root.mxDecryptionResult?.isSafe == true timeLineEvent.root.mxDecryptionResult?.isSafe == true
} }
} }
Log.v("#E2E TEST", "Everybody received the encrypted message and could decrypt")
// Add a new user to the room, and check that he can't decrypt // Add a new user to the room, and check that he can't decrypt
Log.v("#E2E TEST", "Create some new accounts and invite them")
val newAccount = listOf("adam") // , "adam", "manu") val newAccount = listOf("adam") // , "adam", "manu")
.map { .let {
testHelper.createAccount(it, SessionTestParams(true)) cryptoTestHelper.inviteNewUsersAndWaitForThemToJoin(aliceSession, e2eRoomID, it)
} }
newAccount.forEach {
Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
aliceRoomPOV.membershipService().invite(it.myUserId)
}
newAccount.forEach {
testHelper.waitForAndAcceptInviteInRoom(it, e2eRoomID)
}
ensureMembersHaveJoined(testHelper, aliceSession, newAccount, e2eRoomID)
// wait a bit // wait a bit
delay(3_000) delay(3_000)
// check that messages are encrypted (uisi) // check that messages are encrypted (uisi)
newAccount.forEach { otherSession -> newAccount.forEach { otherSession ->
testHelper.retryPeriodically { testHelper.retryWithBackoff(
onFail = {
fail("New Users shouldn't be able to decrypt history")
}
) {
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also { val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also {
Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}") Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
} }
@ -181,7 +144,12 @@ class E2eeSanityTests : InstrumentedTest {
// new members should be able to decrypt it // new members should be able to decrypt it
newAccount.forEach { otherSession -> newAccount.forEach { otherSession ->
testHelper.retryPeriodically { // ("${otherSession.myUserId} should be able to decrypt")
testHelper.retryWithBackoff(
onFail = {
fail("New user ${otherSession.myUserId.take(10)} should be able to decrypt the second message")
}
) {
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(secondSentEventId!!).also { val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(secondSentEventId!!).also {
Log.v("#E2E TEST", "Second Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}") Log.v("#E2E TEST", "Second Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
} }
@ -223,13 +191,10 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Create and start key backup for bob ...") Log.v("#E2E TEST", "Create and start key backup for bob ...")
val bobKeysBackupService = bobSession.cryptoService().keysBackupService() val bobKeysBackupService = bobSession.cryptoService().keysBackupService()
val keyBackupPassword = "FooBarBaz" val keyBackupPassword = "FooBarBaz"
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> { val megolmBackupCreationInfo = bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null)
bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it) val version = bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo)
}
val version = testHelper.waitForCallback<KeysVersion> { Log.v("#E2E TEST", "... Key backup started and enabled for bob: version:$version")
bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it)
}
Log.v("#E2E TEST", "... Key backup started and enabled for bob")
// Bob session should now have // Bob session should now have
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!! val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
@ -242,7 +207,11 @@ class E2eeSanityTests : InstrumentedTest {
sentEventIds.add(it) sentEventIds.add(it)
} }
testHelper.retryPeriodically { testHelper.retryWithBackoff(
onFail = {
fail("Bob should be able to decrypt all messages")
}
) {
val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId) val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
timeLineEvent != null && timeLineEvent != null &&
timeLineEvent.isEncrypted() && timeLineEvent.isEncrypted() &&
@ -256,7 +225,14 @@ class E2eeSanityTests : InstrumentedTest {
// Let's wait a bit to be sure that bob has backed up the session // Let's wait a bit to be sure that bob has backed up the session
Log.v("#E2E TEST", "Force key backup for Bob...") Log.v("#E2E TEST", "Force key backup for Bob...")
testHelper.waitForCallback<Unit> { bobKeysBackupService.backupAllGroupSessions(null, it) } testHelper.retryWithBackoff(
onFail = {
fail("All keys should be backedup")
}
) {
Log.v("#E2E TEST", "backedUp=${ bobKeysBackupService.getTotalNumbersOfBackedUpKeys()}, known=${bobKeysBackupService.getTotalNumbersOfKeys()}")
bobKeysBackupService.getTotalNumbersOfBackedUpKeys() == bobKeysBackupService.getTotalNumbersOfKeys()
}
Log.v("#E2E TEST", "... Key backup done for Bob") Log.v("#E2E TEST", "... Key backup done for Bob")
// Now lets logout both alice and bob to ensure that we won't have any gossiping // Now lets logout both alice and bob to ensure that we won't have any gossiping
@ -276,7 +252,7 @@ class E2eeSanityTests : InstrumentedTest {
// check that bob can't currently decrypt // check that bob can't currently decrypt
Log.v("#E2E TEST", "check that bob can't currently decrypt") Log.v("#E2E TEST", "check that bob can't currently decrypt")
sentEventIds.forEach { sentEventId -> sentEventIds.forEach { sentEventId ->
testHelper.retryPeriodically { testHelper.retryWithBackoff {
val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)?.also { val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)?.also {
Log.v("#E2E TEST", "Event seen by new user ${it.root.getClearType()}|${it.root.mCryptoError}") Log.v("#E2E TEST", "Event seen by new user ${it.root.getClearType()}|${it.root.mCryptoError}")
} }
@ -284,33 +260,37 @@ class E2eeSanityTests : InstrumentedTest {
} }
} }
// after initial sync events are not decrypted, so we have to try manually // after initial sync events are not decrypted, so we have to try manually
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) // TODO CHANGE WHEN AVAILABLE FROM RUST
cryptoTestHelper.ensureCannotDecrypt(
sentEventIds,
newBobSession,
e2eRoomID,
MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID
) // MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
// Let's now import keys from backup // Let's now import keys from backup
Log.v("#E2E TEST", "Restore backup for the new session")
newBobSession.cryptoService().keysBackupService().let { kbs -> newBobSession.cryptoService().keysBackupService().let { kbs ->
val keyVersionResult = testHelper.waitForCallback<KeysVersionResult?> { val keyVersionResult = kbs.getVersion(version.version)
kbs.getVersion(version.version, it)
}
val importedResult = testHelper.waitForCallback<ImportRoomKeysResult> { val importedResult = kbs.restoreKeyBackupWithPassword(
kbs.restoreKeyBackupWithPassword(
keyVersionResult!!, keyVersionResult!!,
keyBackupPassword, keyBackupPassword,
null, null,
null, null,
null, null,
it
) )
}
assertEquals(3, importedResult.totalNumberOfKeys) assertEquals(3, importedResult.totalNumberOfKeys)
} }
// ensure bob can now decrypt // ensure bob can now decrypt
Log.v("#E2E TEST", "Check that bob can decrypt now")
cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText) cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
// Check key trust // Check key trust
Log.v("#E2E TEST", "Check key safety")
sentEventIds.forEach { sentEventId -> sentEventIds.forEach { sentEventId ->
val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)!! val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)!!
val result = newBobSession.cryptoService().decryptEvent(timelineEvent.root, "") val result = newBobSession.cryptoService().decryptEvent(timelineEvent.root, "")
@ -342,7 +322,11 @@ class E2eeSanityTests : InstrumentedTest {
sentEventIds.add(it) sentEventIds.add(it)
} }
testHelper.retryPeriodically { testHelper.retryWithBackoff(
onFail = {
fail("${bobSession.myUserId.take(10)} should be able to decrypt message sent by alice}")
}
) {
val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId) val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
timeLineEvent != null && timeLineEvent != null &&
timeLineEvent.isEncrypted() && timeLineEvent.isEncrypted() &&
@ -366,7 +350,7 @@ class E2eeSanityTests : InstrumentedTest {
// Try to request // Try to request
sentEventIds.forEach { sentEventId -> sentEventIds.forEach { sentEventId ->
val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
newBobSession.cryptoService().requestRoomKeyForEvent(event) newBobSession.cryptoService().reRequestRoomKeyForEvent(event)
} }
// Ensure that new bob still can't decrypt (keys must have been withheld) // Ensure that new bob still can't decrypt (keys must have been withheld)
@ -391,12 +375,14 @@ class E2eeSanityTests : InstrumentedTest {
// } // }
// } // }
// */
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null) cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
// Now mark new bob session as verified // Now mark new bob session as verified
bobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId!!) bobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId)
newBobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(bobSession.myUserId, bobSession.sessionParams.deviceId!!) newBobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(bobSession.myUserId, bobSession.sessionParams.deviceId)
// now let new session re-request // now let new session re-request
sentEventIds.forEach { sentEventId -> sentEventIds.forEach { sentEventId ->
@ -431,7 +417,7 @@ class E2eeSanityTests : InstrumentedTest {
firstMessage.let { text -> firstMessage.let { text ->
firstEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!! firstEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
testHelper.retryPeriodically { testHelper.retryWithBackoff {
val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId) val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
timeLineEvent != null && timeLineEvent != null &&
timeLineEvent.isEncrypted() && timeLineEvent.isEncrypted() &&
@ -457,7 +443,7 @@ class E2eeSanityTests : InstrumentedTest {
secondMessage.let { text -> secondMessage.let { text ->
secondEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!! secondEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
testHelper.retryPeriodically { testHelper.retryWithBackoff {
val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId) val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
timeLineEvent != null && timeLineEvent != null &&
timeLineEvent.isEncrypted() && timeLineEvent.isEncrypted() &&
@ -488,11 +474,11 @@ class E2eeSanityTests : InstrumentedTest {
// Now let's verify bobs session, and re-request keys // Now let's verify bobs session, and re-request keys
bobSessionWithBetterKey.cryptoService() bobSessionWithBetterKey.cryptoService()
.verificationService() .verificationService()
.markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId!!) .markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId)
newBobSession.cryptoService() newBobSession.cryptoService()
.verificationService() .verificationService()
.markedLocallyAsManuallyVerified(bobSessionWithBetterKey.myUserId, bobSessionWithBetterKey.sessionParams.deviceId!!) .markedLocallyAsManuallyVerified(bobSessionWithBetterKey.myUserId, bobSessionWithBetterKey.sessionParams.deviceId)
// now let new session request // now let new session request
newBobSession.cryptoService().reRequestRoomKeyForEvent(firstEventNewBobPov.root) newBobSession.cryptoService().reRequestRoomKeyForEvent(firstEventNewBobPov.root)
@ -501,7 +487,7 @@ class E2eeSanityTests : InstrumentedTest {
// old session should have shared the key at earliest known index now // old session should have shared the key at earliest known index now
// we should be able to decrypt both // we should be able to decrypt both
testHelper.retryPeriodically { testHelper.retryWithBackoff {
val canDecryptFirst = try { val canDecryptFirst = try {
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "") newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
true true
@ -524,7 +510,7 @@ class E2eeSanityTests : InstrumentedTest {
val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60)) val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
timeline.start() timeline.start()
testHelper.retryPeriodically { testHelper.retryWithBackoff {
val decryptedMsg = timeline.getSnapshot() val decryptedMsg = timeline.getSnapshot()
.filter { it.root.getClearType() == EventType.MESSAGE } .filter { it.root.getClearType() == EventType.MESSAGE }
.also { list -> .also { list ->
@ -543,76 +529,76 @@ class E2eeSanityTests : InstrumentedTest {
/** /**
* Test that if a better key is forwared (lower index, it is then used) * Test that if a better key is forwared (lower index, it is then used)
*/ */
@Test // @Test
fun testASelfInteractiveVerificationAndGossip() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> // fun testASelfInteractiveVerificationAndGossip() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
//
val aliceSession = testHelper.createAccount("alice", SessionTestParams(true)) // val aliceSession = testHelper.createAccount("alice", SessionTestParams(true))
cryptoTestHelper.bootstrapSecurity(aliceSession) // cryptoTestHelper.bootstrapSecurity(aliceSession)
//
// now let's create a new login from alice // // now let's create a new login from alice
//
val aliceNewSession = testHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true)) // val aliceNewSession = testHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
//
val deferredOldCode = aliceSession.cryptoService().verificationService().readOldVerificationCodeAsync(this, aliceSession.myUserId) // val deferredOldCode = aliceSession.cryptoService().verificationService().readOldVerificationCodeAsync(this, aliceSession.myUserId)
val deferredNewCode = aliceNewSession.cryptoService().verificationService().readNewVerificationCodeAsync(this, aliceSession.myUserId) // val deferredNewCode = aliceNewSession.cryptoService().verificationService().readNewVerificationCodeAsync(this, aliceSession.myUserId)
// initiate self verification // // initiate self verification
aliceSession.cryptoService().verificationService().requestKeyVerification( // aliceSession.cryptoService().verificationService().requestSelfKeyVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), // listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
aliceNewSession.myUserId, // // aliceNewSession.myUserId,
listOf(aliceNewSession.sessionParams.deviceId!!) // // listOf(aliceNewSession.sessionParams.deviceId!!)
) // )
//
val (oldCode, newCode) = awaitAll(deferredOldCode, deferredNewCode) // val (oldCode, newCode) = awaitAll(deferredOldCode, deferredNewCode)
//
assertEquals("Decimal code should have matched", oldCode, newCode) // assertEquals("Decimal code should have matched", oldCode, newCode)
//
// Assert that devices are verified // // Assert that devices are verified
val newDeviceFromOldPov: CryptoDeviceInfo? = // val newDeviceFromOldPov: CryptoDeviceInfo? =
aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId) // aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId)
val oldDeviceFromNewPov: CryptoDeviceInfo? = // val oldDeviceFromNewPov: CryptoDeviceInfo? =
aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId) // aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
//
Assert.assertTrue("new device should be verified from old point of view", newDeviceFromOldPov!!.isVerified) // Assert.assertTrue("new device should be verified from old point of view", newDeviceFromOldPov!!.isVerified)
Assert.assertTrue("old device should be verified from new point of view", oldDeviceFromNewPov!!.isVerified) // Assert.assertTrue("old device should be verified from new point of view", oldDeviceFromNewPov!!.isVerified)
//
// wait for secret gossiping to happen // // wait for secret gossiping to happen
testHelper.retryPeriodically { // testHelper.retryPeriodically {
aliceNewSession.cryptoService().crossSigningService().allPrivateKeysKnown() // aliceNewSession.cryptoService().crossSigningService().allPrivateKeysKnown()
} // }
//
testHelper.retryPeriodically { // testHelper.retryPeriodically {
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null // aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null
} // }
//
assertEquals( // assertEquals(
"MSK Private parts should be the same", // "MSK Private parts should be the same",
aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master, // aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master,
aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master // aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master
) // )
assertEquals( // assertEquals(
"USK Private parts should be the same", // "USK Private parts should be the same",
aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user, // aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user,
aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user // aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user
) // )
//
assertEquals( // assertEquals(
"SSK Private parts should be the same", // "SSK Private parts should be the same",
aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned, // aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned,
aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned // aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned
) // )
//
// Let's check that we have the megolm backup key // // Let's check that we have the megolm backup key
assertEquals( // assertEquals(
"Megolm key should be the same", // "Megolm key should be the same",
aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey, // aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey,
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey // aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey
) // )
assertEquals( // assertEquals(
"Megolm version should be the same", // "Megolm version should be the same",
aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version, // aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version,
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version // aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version
) // )
} // }
@Test @Test
fun test_EncryptionDoesNotHinderVerification() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> fun test_EncryptionDoesNotHinderVerification() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
@ -625,26 +611,23 @@ class E2eeSanityTests : InstrumentedTest {
user = aliceSession.myUserId, user = aliceSession.myUserId,
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
) )
val bobAuthParams = UserPasswordAuth( val bobAuthParams = UserPasswordAuth(
user = bobSession!!.myUserId, user = bobSession!!.myUserId,
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
) )
testHelper.waitForCallback {
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(aliceAuthParams) promise.resume(aliceAuthParams)
} }
}, it) })
}
testHelper.waitForCallback {
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(bobAuthParams) promise.resume(bobAuthParams)
} }
}, it) })
}
// add a second session for bob but not cross signed // add a second session for bob but not cross signed
@ -660,11 +643,11 @@ class E2eeSanityTests : InstrumentedTest {
// wait for it to be synced back the other side // wait for it to be synced back the other side
Timber.v("#TEST: Wait for message to be synced back") Timber.v("#TEST: Wait for message to be synced back")
testHelper.retryPeriodically { testHelper.retryWithBackoff {
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null
} }
testHelper.retryPeriodically { testHelper.retryWithBackoff {
secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null
} }
@ -681,11 +664,11 @@ class E2eeSanityTests : InstrumentedTest {
val secondEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "World")!! val secondEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "World")!!
Timber.v("#TEST: Wait for message to be synced back") Timber.v("#TEST: Wait for message to be synced back")
testHelper.retryPeriodically { testHelper.retryWithBackoff {
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null
} }
testHelper.retryPeriodically { testHelper.retryWithBackoff {
secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null
} }
@ -693,94 +676,94 @@ class E2eeSanityTests : InstrumentedTest {
cryptoTestHelper.ensureCannotDecrypt(listOf(secondEvent), secondBobSession, cryptoTestData.roomId) cryptoTestHelper.ensureCannotDecrypt(listOf(secondEvent), secondBobSession, cryptoTestData.roomId)
} }
private suspend fun VerificationService.readOldVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> { // private suspend fun VerificationService.readOldVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
return scope.async { // return scope.async {
suspendCancellableCoroutine { continuation -> // suspendCancellableCoroutine { continuation ->
var oldCode: String? = null // var oldCode: String? = null
val listener = object : VerificationService.Listener { // val listener = object : VerificationService.Listener {
//
override fun verificationRequestUpdated(pr: PendingVerificationRequest) { // override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
val readyInfo = pr.readyInfo // val readyInfo = pr.readyInfo
if (readyInfo != null) { // if (readyInfo != null) {
beginKeyVerification( // beginKeyVerification(
VerificationMethod.SAS, // VerificationMethod.SAS,
userId, // userId,
readyInfo.fromDevice, // readyInfo.fromDevice,
readyInfo.transactionId // readyInfo.transactionId
//
) // )
} // }
} // }
//
override fun transactionUpdated(tx: VerificationTransaction) { // override fun transactionUpdated(tx: VerificationTransaction) {
Log.d("##TEST", "exitsingPov: $tx") // Log.d("##TEST", "exitsingPov: $tx")
val sasTx = tx as OutgoingSasVerificationTransaction // val sasTx = tx as OutgoingSasVerificationTransaction
when (sasTx.uxState) { // when (sasTx.uxState) {
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> { // OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
// for the test we just accept? // // for the test we just accept?
oldCode = sasTx.getDecimalCodeRepresentation() // oldCode = sasTx.getDecimalCodeRepresentation()
sasTx.userHasVerifiedShortCode() // sasTx.userHasVerifiedShortCode()
} // }
OutgoingSasVerificationTransaction.UxState.VERIFIED -> { // OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
removeListener(this) // removeListener(this)
// we can release this latch? // // we can release this latch?
continuation.resume(oldCode!!) // continuation.resume(oldCode!!)
} // }
else -> Unit // else -> Unit
} // }
} // }
} // }
addListener(listener) // addListener(listener)
continuation.invokeOnCancellation { removeListener(listener) } // continuation.invokeOnCancellation { removeListener(listener) }
} // }
} // }
} // }
//
private suspend fun VerificationService.readNewVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> { // private suspend fun VerificationService.readNewVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
return scope.async { // return scope.async {
suspendCancellableCoroutine { continuation -> // suspendCancellableCoroutine { continuation ->
var newCode: String? = null // var newCode: String? = null
//
val listener = object : VerificationService.Listener { // val listener = object : VerificationService.Listener {
//
override fun verificationRequestCreated(pr: PendingVerificationRequest) { // override fun verificationRequestCreated(pr: PendingVerificationRequest) {
// let's ready // // let's ready
readyPendingVerification( // readyPendingVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), // listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
userId, // userId,
pr.transactionId!! // pr.transactionId!!
) // )
} // }
//
var matchOnce = true // var matchOnce = true
override fun transactionUpdated(tx: VerificationTransaction) { // override fun transactionUpdated(tx: VerificationTransaction) {
Log.d("##TEST", "newPov: $tx") // Log.d("##TEST", "newPov: $tx")
//
val sasTx = tx as IncomingSasVerificationTransaction // val sasTx = tx as IncomingSasVerificationTransaction
when (sasTx.uxState) { // when (sasTx.uxState) {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { // IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
// no need to accept as there was a request first it will auto accept // // no need to accept as there was a request first it will auto accept
} // }
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { // IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
if (matchOnce) { // if (matchOnce) {
sasTx.userHasVerifiedShortCode() // sasTx.userHasVerifiedShortCode()
newCode = sasTx.getDecimalCodeRepresentation() // newCode = sasTx.getDecimalCodeRepresentation()
matchOnce = false // matchOnce = false
} // }
} // }
IncomingSasVerificationTransaction.UxState.VERIFIED -> { // IncomingSasVerificationTransaction.UxState.VERIFIED -> {
removeListener(this) // removeListener(this)
continuation.resume(newCode!!) // continuation.resume(newCode!!)
} // }
else -> Unit // else -> Unit
} // }
} // }
} // }
addListener(listener) // addListener(listener)
continuation.invokeOnCancellation { removeListener(listener) } // continuation.invokeOnCancellation { removeListener(listener) }
} // }
} // }
} // }
private suspend fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) { private suspend fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {
testHelper.retryPeriodically { testHelper.retryPeriodically {

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto
import android.util.Log import android.util.Log
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import org.amshove.kluent.fail
import org.amshove.kluent.internal.assertEquals import org.amshove.kluent.internal.assertEquals
import org.amshove.kluent.internal.assertNotEquals import org.amshove.kluent.internal.assertNotEquals
import org.junit.Assert import org.junit.Assert
@ -42,7 +43,6 @@ import org.matrix.android.sdk.api.session.room.model.shouldShareHistory
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.SessionTestParams
import org.matrix.android.sdk.common.wrapWithTimeout
@RunWith(JUnit4::class) @RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM) @FixMethodOrder(MethodSorters.JVM)
@ -99,11 +99,17 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, aliceMessageText, testHelper) val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, aliceMessageText, testHelper)
Assert.assertTrue("Message should be sent", aliceMessageId != null) Assert.assertTrue("Message should be sent", aliceMessageId != null)
Log.v("#E2E TEST", "Alice sent message to roomId: $e2eRoomID") Log.v("#E2E TEST", "Alice has sent message to roomId: $e2eRoomID")
// Bob should be able to decrypt the message // Bob should be able to decrypt the message
testHelper.retryPeriodically { testHelper.retryWithBackoff(
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!) onFail = {
fail("Bob should be able to decrypt $aliceMessageId")
}
) {
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)?.also {
Log.v("#E2E TEST", "Bob sees ${it.root.getClearType()}")
}
(timelineEvent != null && (timelineEvent != null &&
timelineEvent.isEncrypted() && timelineEvent.isEncrypted() &&
timelineEvent.root.getClearType() == EventType.MESSAGE && timelineEvent.root.getClearType() == EventType.MESSAGE &&
@ -135,7 +141,11 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
null null
-> { -> {
// Aris should be able to decrypt the message // Aris should be able to decrypt the message
testHelper.retryPeriodically { testHelper.retryWithBackoff(
onFail = {
fail("Aris should be able to decrypt $aliceMessageId")
}
) {
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!) val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
(timelineEvent != null && (timelineEvent != null &&
timelineEvent.isEncrypted() && timelineEvent.isEncrypted() &&
@ -151,7 +161,11 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
RoomHistoryVisibility.INVITED, RoomHistoryVisibility.INVITED,
RoomHistoryVisibility.JOINED -> { RoomHistoryVisibility.JOINED -> {
// Aris should not even be able to get the message // Aris should not even be able to get the message
testHelper.retryPeriodically { testHelper.retryWithBackoff(
onFail = {
fail("Aris should not even be able to get the message")
}
) {
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID) val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)
?.timelineService() ?.timelineService()
?.getTimelineEvent(aliceMessageId!!) ?.getTimelineEvent(aliceMessageId!!)
@ -258,11 +272,17 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
// Bob should be able to decrypt the message // Bob should be able to decrypt the message
var firstAliceMessageMegolmSessionId: String? = null var firstAliceMessageMegolmSessionId: String? = null
val bobRoomPov = bobSession.roomService().getRoom(e2eRoomID) val bobRoomPov = bobSession.roomService().getRoom(e2eRoomID)!!
testHelper.retryPeriodically { testHelper.retryWithBackoff(
onFail = {
fail("Bob should be able to decrypt $aliceMessageId")
}
) {
val timelineEvent = bobRoomPov val timelineEvent = bobRoomPov
?.timelineService() .timelineService()
?.getTimelineEvent(aliceMessageId!!) .getTimelineEvent(aliceMessageId!!)?.also {
Log.v("#E2E TEST ROTATION", "Bob sees ${it.root.getClearType()}")
}
(timelineEvent != null && (timelineEvent != null &&
timelineEvent.isEncrypted() && timelineEvent.isEncrypted() &&
timelineEvent.root.getClearType() == EventType.MESSAGE).also { timelineEvent.root.getClearType() == EventType.MESSAGE).also {
@ -279,11 +299,17 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
Assert.assertNotNull("megolm session id can't be null", firstAliceMessageMegolmSessionId) Assert.assertNotNull("megolm session id can't be null", firstAliceMessageMegolmSessionId)
var secondAliceMessageSessionId: String? = null var secondAliceMessageSessionId: String? = null
sendMessageInRoom(aliceRoomPOV, "Other msg", testHelper)?.let { secondMessage -> sendMessageInRoom(aliceRoomPOV, "Other msg", testHelper)!!.let { secondMessage ->
testHelper.retryPeriodically { testHelper.retryWithBackoff(
onFail = {
fail("Bob should be able to decrypt the second message $secondMessage")
}
) {
val timelineEvent = bobRoomPov val timelineEvent = bobRoomPov
?.timelineService() .timelineService()
?.getTimelineEvent(secondMessage) .getTimelineEvent(secondMessage)?.also {
Log.v("#E2E TEST ROTATION", "Bob sees ${it.root.getClearType()}")
}
(timelineEvent != null && (timelineEvent != null &&
timelineEvent.isEncrypted() && timelineEvent.isEncrypted() &&
timelineEvent.root.getClearType() == EventType.MESSAGE).also { timelineEvent.root.getClearType() == EventType.MESSAGE).also {
@ -309,29 +335,44 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
historyVisibilityStr = nextRoomHistoryVisibility.historyVisibilityStr historyVisibilityStr = nextRoomHistoryVisibility.historyVisibilityStr
).toContent() ).toContent()
) )
Log.v("#E2E TEST ROTATION", "State update sent")
// ensure that the state did synced down // ensure that the state did synced down
testHelper.retryPeriodically { testHelper.retryWithBackoff(
aliceRoomPOV.stateService().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)?.content onFail = {
fail("Alice state should be updated to ${nextRoomHistoryVisibility.historyVisibilityStr}")
}
) {
aliceRoomPOV.stateService().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)
?.content
?.also {
Log.v("#E2E TEST ROTATION", "Alice sees state as $it")
}
?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility == nextRoomHistoryVisibility.historyVisibility ?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility == nextRoomHistoryVisibility.historyVisibility
} }
testHelper.retryPeriodically { // testHelper.retryPeriodically {
val roomVisibility = aliceSession.getRoom(e2eRoomID)!! // val roomVisibility = aliceSession.getRoom(e2eRoomID)!!
.stateService() // .stateService()
.getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty) // .getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)
?.content // ?.content
?.toModel<RoomHistoryVisibilityContent>() // ?.toModel<RoomHistoryVisibilityContent>()
Log.v("#E2E TEST ROTATION", "Room visibility changed from: ${initRoomHistoryVisibility.name} to: ${roomVisibility?.historyVisibility?.name}") // Log.v("#E2E TEST ROTATION", "Room visibility changed from: ${initRoomHistoryVisibility.name} to: ${roomVisibility?.historyVisibility?.name}")
roomVisibility?.historyVisibility == nextRoomHistoryVisibility.historyVisibility // roomVisibility?.historyVisibility == nextRoomHistoryVisibility.historyVisibility
} // }
var aliceThirdMessageSessionId: String? = null var aliceThirdMessageSessionId: String? = null
sendMessageInRoom(aliceRoomPOV, "Message after visibility change", testHelper)?.let { thirdMessage -> sendMessageInRoom(aliceRoomPOV, "Message after visibility change", testHelper)!!.let { thirdMessage ->
testHelper.retryPeriodically { testHelper.retryWithBackoff(
onFail = {
fail("Bob should be able to decrypt $thirdMessage")
}
) {
val timelineEvent = bobRoomPov val timelineEvent = bobRoomPov
?.timelineService() .timelineService()
?.getTimelineEvent(thirdMessage) .getTimelineEvent(thirdMessage)?.also {
Log.v("#E2E TEST ROTATION", "Bob sees ${it.root.getClearType()}")
}
(timelineEvent != null && (timelineEvent != null &&
timelineEvent.isEncrypted() && timelineEvent.isEncrypted() &&
timelineEvent.root.getClearType() == EventType.MESSAGE).also { timelineEvent.root.getClearType() == EventType.MESSAGE).also {
@ -364,7 +405,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
} }
private suspend fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String, testHelper: CommonTestHelper) { private suspend fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String, testHelper: CommonTestHelper) {
testHelper.retryPeriodically { testHelper.retryWithBackoff {
otherAccounts.map { otherAccounts.map {
aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership
}.all { }.all {
@ -374,7 +415,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
} }
private suspend fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String, testHelper: CommonTestHelper) { private suspend fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String, testHelper: CommonTestHelper) {
testHelper.retryPeriodically { testHelper.retryWithBackoff {
val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID) val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID)
(roomSummary != null && roomSummary.membership == Membership.INVITE).also { (roomSummary != null && roomSummary.membership == Membership.INVITE).also {
if (it) { if (it) {
@ -383,17 +424,15 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
} }
} }
wrapWithTimeout(60_000) {
Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID") Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID")
try { try {
otherSession.roomService().joinRoom(e2eRoomID) otherSession.roomService().joinRoom(e2eRoomID)
} catch (ex: JoinRoomFailure.JoinedWithTimeout) { } catch (ex: JoinRoomFailure.JoinedWithTimeout) {
// it's ok we will wait after // it's ok we will wait after
} }
}
Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...") Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...")
testHelper.retryPeriodically { testHelper.retryWithBackoff {
val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID) val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID)
roomSummary != null && roomSummary.membership == Membership.JOIN roomSummary != null && roomSummary.membership == Membership.JOIN
} }

View File

@ -20,6 +20,7 @@ import org.amshove.kluent.shouldBeNull
import org.amshove.kluent.shouldBeTrue import org.amshove.kluent.shouldBeTrue
import org.junit.Test import org.junit.Test
import org.matrix.android.sdk.api.util.fromBase64 import org.matrix.android.sdk.api.util.fromBase64
import org.matrix.android.sdk.api.util.fromBase64Safe
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
class ExtensionsKtTest { class ExtensionsKtTest {

View File

@ -35,8 +35,6 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified
import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified
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.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.SessionTestParams
@ -54,7 +52,6 @@ class XSigningTest : InstrumentedTest {
fun test_InitializeAndStoreKeys() = runSessionTest(context()) { testHelper -> fun test_InitializeAndStoreKeys() = runSessionTest(context()) { testHelper ->
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
testHelper.waitForCallback<Unit> {
aliceSession.cryptoService().crossSigningService() aliceSession.cryptoService().crossSigningService()
.initializeCrossSigning(object : UserInteractiveAuthInterceptor { .initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
@ -66,10 +63,10 @@ class XSigningTest : InstrumentedTest {
) )
) )
} }
}, it) })
}
val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys() val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
val masterPubKey = myCrossSigningKeys?.masterKey() val masterPubKey = myCrossSigningKeys?.masterKey()
assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey) assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey)
val selfSigningKey = myCrossSigningKeys?.selfSigningKey() val selfSigningKey = myCrossSigningKeys?.selfSigningKey()
@ -79,13 +76,14 @@ class XSigningTest : InstrumentedTest {
assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted() == true) assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted() == true)
assertTrue("Signing Keys should be trusted", aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId).isVerified()) val userTrustResult = aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId)
assertTrue("Signing Keys should be trusted", userTrustResult.isVerified())
testHelper.signOutAndClose(aliceSession) testHelper.signOutAndClose(aliceSession)
} }
@Test @Test
fun test_CrossSigningCheckBobSeesTheKeys() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> fun test_CrossSigningCheckBobSeesTheKeys() = runCryptoTest(context()) { cryptoTestHelper, _ ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
@ -100,39 +98,30 @@ class XSigningTest : InstrumentedTest {
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
) )
testHelper.waitForCallback<Unit> {
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(aliceAuthParams) promise.resume(aliceAuthParams)
} }
}, it) })
}
testHelper.waitForCallback<Unit> {
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(bobAuthParams) promise.resume(bobAuthParams)
} }
}, it) })
}
// Check that alice can see bob keys // Check that alice can see bob keys
testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) } aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobSession.myUserId), true)
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId) val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey()) assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey()) assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey())
assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey()) assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey())
assertEquals( val myKeys = bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
"Bob keys from alice pov should match",
bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, myKeys?.masterKey()?.unpaddedBase64PublicKey)
bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, myKeys?.selfSigningKey()?.unpaddedBase64PublicKey)
)
assertEquals(
"Bob keys from alice pov should match",
bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey,
bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey
)
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted()) assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
} }
@ -153,40 +142,34 @@ class XSigningTest : InstrumentedTest {
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
) )
testHelper.waitForCallback<Unit> {
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(aliceAuthParams) promise.resume(aliceAuthParams)
} }
}, it) })
}
testHelper.waitForCallback<Unit> {
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(bobAuthParams) promise.resume(bobAuthParams)
} }
}, it) })
}
// Check that alice can see bob keys // Check that alice can see bob keys
val bobUserId = bobSession.myUserId val bobUserId = bobSession.myUserId
testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) } aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), true)
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId) val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false) assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
testHelper.waitForCallback<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) } aliceSession.cryptoService().crossSigningService().trustUser(bobUserId)
// Now bobs logs in on a new device and verifies it // Now bobs logs in on a new device and verifies it
// We will want to test that in alice POV, this new device would be trusted by cross signing // We will want to test that in alice POV, this new device would be trusted by cross signing
val bobSession2 = testHelper.logIntoAccount(bobUserId, SessionTestParams(true)) val bobSession2 = testHelper.logIntoAccount(bobUserId, SessionTestParams(true))
val bobSecondDeviceId = bobSession2.sessionParams.deviceId!! val bobSecondDeviceId = bobSession2.sessionParams.deviceId
// Check that bob first session sees the new login // Check that bob first session sees the new login
val data = testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> { val data = bobSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), true)
bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
}
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) { if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
fail("Bob should see the new device") fail("Bob should see the new device")
@ -196,14 +179,10 @@ class XSigningTest : InstrumentedTest {
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice) assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
// Manually mark it as trusted from first session // Manually mark it as trusted from first session
testHelper.waitForCallback<Unit> { bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId)
bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId, it)
}
// Now alice should cross trust bob's second device // Now alice should cross trust bob's second device
val data2 = testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> { val data2 = aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), true)
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
}
// check that the device is seen // check that the device is seen
if (data2.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) { if (data2.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
@ -230,20 +209,16 @@ class XSigningTest : InstrumentedTest {
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
) )
testHelper.waitForCallback<Unit> {
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(aliceAuthParams) promise.resume(aliceAuthParams)
} }
}, it) })
}
testHelper.waitForCallback<Unit> {
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(bobAuthParams) promise.resume(bobAuthParams)
} }
}, it) })
}
cryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, cryptoTestData.roomId) cryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, cryptoTestData.roomId)
@ -267,13 +242,11 @@ class XSigningTest : InstrumentedTest {
.getUserCrossSigningKeys(bobSession.myUserId)!! .getUserCrossSigningKeys(bobSession.myUserId)!!
.masterKey()!!.unpaddedBase64PublicKey!! .masterKey()!!.unpaddedBase64PublicKey!!
testHelper.waitForCallback<Unit> {
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
promise.resume(bobAuthParams) promise.resume(bobAuthParams)
} }
}, it) })
}
testHelper.retryPeriodically { testHelper.retryPeriodically {
val newBobMsk = aliceSession.cryptoService().crossSigningService() val newBobMsk = aliceSession.cryptoService().crossSigningService()

View File

@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.crypto.gossiping
import android.util.Log import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertTrue import junit.framework.TestCase.assertTrue
import org.amshove.kluent.internal.assertEquals
import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeEqualTo
import org.junit.Assert import org.junit.Assert
import org.junit.Assert.assertNull import org.junit.Assert.assertNull
@ -100,7 +100,7 @@ class KeyShareTests : InstrumentedTest {
// Try to request // Try to request
aliceSession2.cryptoService().enableKeyGossiping(true) aliceSession2.cryptoService().enableKeyGossiping(true)
aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root) aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId
@ -163,22 +163,26 @@ class KeyShareTests : InstrumentedTest {
// Mark the device as trusted // Mark the device as trusted
Log.v("#TEST", "=======> Alice device 1 is ${aliceSession.sessionParams.deviceId}|${aliceSession.cryptoService().getMyDevice().identityKey()}") Log.v("#TEST", "=======> Alice device 1 is ${aliceSession.sessionParams.deviceId}|${aliceSession.cryptoService().getMyCryptoDevice().identityKey()}")
val aliceSecondSession = aliceSession2.cryptoService().getMyDevice() val aliceSecondSession = aliceSession2.cryptoService().getMyCryptoDevice()
Log.v("#TEST", "=======> Alice device 2 is ${aliceSession2.sessionParams.deviceId}|${aliceSecondSession.identityKey()}") Log.v("#TEST", "=======> Alice device 2 is ${aliceSession2.sessionParams.deviceId}|${aliceSecondSession.identityKey()}")
aliceSession.cryptoService().setDeviceVerification( aliceSession.cryptoService().setDeviceVerification(
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId, DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
aliceSession2.sessionParams.deviceId ?: "" aliceSession2.sessionParams.deviceId
) )
// We only accept forwards from trusted session, so we need to trust on other side to // We only accept forwards from trusted session, so we need to trust on other side to
aliceSession2.cryptoService().setDeviceVerification( aliceSession2.cryptoService().setDeviceVerification(
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId, DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
aliceSession.sessionParams.deviceId ?: "" aliceSession.sessionParams.deviceId
) )
aliceSession.cryptoService().deviceWithIdentityKey(aliceSecondSession.identityKey()!!, MXCRYPTO_ALGORITHM_OLM)!!.isVerified shouldBeEqualTo true aliceSession.cryptoService().deviceWithIdentityKey(
aliceSecondSession.userId,
aliceSecondSession.identityKey()!!,
MXCRYPTO_ALGORITHM_OLM
)!!.isVerified shouldBeEqualTo true
// Re request // Re request
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root) aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
@ -257,11 +261,11 @@ class KeyShareTests : InstrumentedTest {
outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId } outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
ownDeviceReply != null && ownDeviceReply.result is RequestResult.Success ownDeviceReply != null && ownDeviceReply.result is RequestResult.Success
} }
commonTestHelper.signOutAndClose(aliceSession)
commonTestHelper.signOutAndClose(aliceNewSession)
} }
/**
* Tests that keys reshared with own verified session are done from the earliest known index
*/
@Test @Test
fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() = runCryptoTest( fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() = runCryptoTest(
context(), context(),
@ -331,10 +335,10 @@ class KeyShareTests : InstrumentedTest {
// Mark the new session as verified // Mark the new session as verified
aliceSession.cryptoService() aliceSession.cryptoService()
.verificationService() .verificationService()
.markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId!!) .markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId)
aliceNewSession.cryptoService() aliceNewSession.cryptoService()
.verificationService() .verificationService()
.markedLocallyAsManuallyVerified(aliceSession.myUserId, aliceSession.sessionParams.deviceId!!) .markedLocallyAsManuallyVerified(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
// Let's now try to request // Let's now try to request
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvents.first().root) aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvents.first().root)
@ -375,9 +379,6 @@ class KeyShareTests : InstrumentedTest {
commonTestHelper.signOutAndClose(bobSession) commonTestHelper.signOutAndClose(bobSession)
} }
/**
* Tests that we don't cancel a request to early on first forward if the index is not good enough
*/
@Test @Test
fun test_dontCancelToEarly() = runCryptoTest( fun test_dontCancelToEarly() = runCryptoTest(
context(), context(),
@ -419,10 +420,10 @@ class KeyShareTests : InstrumentedTest {
// Mark the new session as verified // Mark the new session as verified
aliceSession.cryptoService() aliceSession.cryptoService()
.verificationService() .verificationService()
.markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId!!) .markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId)
aliceNewSession.cryptoService() aliceNewSession.cryptoService()
.verificationService() .verificationService()
.markedLocallyAsManuallyVerified(aliceSession.myUserId, aliceSession.sessionParams.deviceId!!) .markedLocallyAsManuallyVerified(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
// /!\ Stop initial alice session syncing so that it can't reply // /!\ Stop initial alice session syncing so that it can't reply
aliceSession.cryptoService().enableKeyGossiping(false) aliceSession.cryptoService().enableKeyGossiping(false)

View File

@ -26,7 +26,6 @@ import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.NoOpMatrixCallback
import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.MXCryptoError
@ -71,6 +70,7 @@ class WithHeldTests : InstrumentedTest {
val roomAlicePOV = aliceSession.getRoom(roomId)!! val roomAlicePOV = aliceSession.getRoom(roomId)!!
val bobUnverifiedSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true)) val bobUnverifiedSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
// ============================= // =============================
// ACT // ACT
// ============================= // =============================
@ -155,15 +155,13 @@ class WithHeldTests : InstrumentedTest {
val aliceInterceptor = testHelper.getTestInterceptor(aliceSession) val aliceInterceptor = testHelper.getTestInterceptor(aliceSession)
// Simulate no OTK // Simulate no OTK
aliceInterceptor!!.addRule( aliceInterceptor!!.addRule(MockOkHttpInterceptor.SimpleRule(
MockOkHttpInterceptor.SimpleRule(
"/keys/claim", "/keys/claim",
200, 200,
""" """
{ "one_time_keys" : {} } { "one_time_keys" : {} }
""" """
) ))
)
Log.d("#TEST", "Recovery :${aliceSession.sessionParams.credentials.accessToken}") Log.d("#TEST", "Recovery :${aliceSession.sessionParams.credentials.accessToken}")
val roomAlicePov = aliceSession.getRoom(testData.roomId)!! val roomAlicePov = aliceSession.getRoom(testData.roomId)!!
@ -191,10 +189,7 @@ class WithHeldTests : InstrumentedTest {
// Ensure that alice has marked the session to be shared with bob // Ensure that alice has marked the session to be shared with bob
val sessionId = eventBobPOV!!.root.content.toModel<EncryptedEventContent>()!!.sessionId!! val sessionId = eventBobPOV!!.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
val chainIndex = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject( val chainIndex = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSession.myUserId, bobSession.sessionParams.credentials.deviceId)
bobSession.myUserId,
bobSession.sessionParams.credentials.deviceId
)
Assert.assertEquals("Alice should have marked bob's device for this session", 0, chainIndex) Assert.assertEquals("Alice should have marked bob's device for this session", 0, chainIndex)
// Add a new device for bob // Add a new device for bob
@ -210,10 +205,7 @@ class WithHeldTests : InstrumentedTest {
bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(secondMessageId) != null bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(secondMessageId) != null
} }
val chainIndex2 = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject( val chainIndex2 = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSecondSession.myUserId, bobSecondSession.sessionParams.credentials.deviceId)
bobSecondSession.myUserId,
bobSecondSession.sessionParams.credentials.deviceId
)
Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2) Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2)
@ -243,8 +235,8 @@ class WithHeldTests : InstrumentedTest {
cryptoTestHelper.initializeCrossSigning(bobSecondSession) cryptoTestHelper.initializeCrossSigning(bobSecondSession)
// Trust bob second device from Alice POV // Trust bob second device from Alice POV
aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!, NoOpMatrixCallback()) aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId)
bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId!!, NoOpMatrixCallback()) bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId)
var sessionId: String? = null var sessionId: String? = null
// Check that the // Check that the

View File

@ -19,14 +19,13 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestData import org.matrix.android.sdk.common.CryptoTestData
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
/** /**
* Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword] * Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]
*/ */
internal data class KeysBackupScenarioData( internal data class KeysBackupScenarioData(
val cryptoTestData: CryptoTestData, val cryptoTestData: CryptoTestData,
val aliceKeys: List<MXInboundMegolmSessionWrapper>, val aliceKeysCount: Int,
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult, val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
val aliceSession2: Session val aliceSession2: Session
) { ) {

View File

@ -18,13 +18,10 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import org.junit.Assert import org.junit.Assert
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.common.assertDictEquals import org.matrix.android.sdk.common.assertDictEquals
@ -53,29 +50,22 @@ internal class KeysBackupTestHelper(
waitForKeybackUpBatching() waitForKeybackUpBatching()
val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store // val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
val stateObserver = StateObserver(keysBackup) val stateObserver = StateObserver(keysBackup)
val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100) // val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100)
// - Do an e2e backup to the homeserver // - Do an e2e backup to the homeserver
val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password) val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password)
var lastProgress = 0 testHelper.retryPeriodically {
var lastTotal = 0 keysBackup.getTotalNumbersOfKeys() == keysBackup.getTotalNumbersOfBackedUpKeys()
testHelper.waitForCallback<Unit> {
keysBackup.backupAllGroupSessions(object : ProgressListener {
override fun onProgress(progress: Int, total: Int) {
lastProgress = progress
lastTotal = total
}
}, it)
} }
val totalNumbersOfBackedUpKeys = cryptoTestData.firstSession.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()
Assert.assertEquals(2, lastProgress) Assert.assertEquals(2, totalNumbersOfBackedUpKeys)
Assert.assertEquals(2, lastTotal)
val aliceUserId = cryptoTestData.firstSession.myUserId val aliceUserId = cryptoTestData.firstSession.myUserId
@ -83,19 +73,20 @@ internal class KeysBackupTestHelper(
val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync) val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
// Test check: aliceSession2 has no keys at login // Test check: aliceSession2 has no keys at login
Assert.assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false)) val inboundGroupSessionCount = aliceSession2.cryptoService().inboundGroupSessionsCount(false)
Assert.assertEquals(0, inboundGroupSessionCount)
// Wait for backup state to be NotTrusted // Wait for backup state to be NotTrusted
waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted) waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
return KeysBackupScenarioData( val totalNumbersOfBackedUpKeysFromNewSession = aliceSession2.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()
cryptoTestData,
aliceKeys, return KeysBackupScenarioData(cryptoTestData,
totalNumbersOfBackedUpKeysFromNewSession,
prepareKeysBackupDataResult, prepareKeysBackupDataResult,
aliceSession2 aliceSession2)
)
} }
suspend fun prepareAndCreateKeysBackupData( suspend fun prepareAndCreateKeysBackupData(
@ -104,18 +95,15 @@ internal class KeysBackupTestHelper(
): PrepareKeysBackupDataResult { ): PrepareKeysBackupDataResult {
val stateObserver = StateObserver(keysBackup) val stateObserver = StateObserver(keysBackup)
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> { val megolmBackupCreationInfo = keysBackup.prepareKeysBackupVersion(password, null)
keysBackup.prepareKeysBackupVersion(password, null, it)
}
Assert.assertNotNull(megolmBackupCreationInfo) Assert.assertNotNull(megolmBackupCreationInfo)
Assert.assertFalse("Key backup should not be enabled before creation", keysBackup.isEnabled()) Assert.assertFalse("Key backup should not be enabled before creation", keysBackup.isEnabled())
// Create the version // Create the version
val keysVersion = testHelper.waitForCallback<KeysVersion> { val keysVersion =
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it) keysBackup.createKeysBackupVersion(megolmBackupCreationInfo)
}
Assert.assertNotNull("Key backup version should not be null", keysVersion.version) Assert.assertNotNull("Key backup version should not be null", keysVersion.version)
@ -152,7 +140,7 @@ internal class KeysBackupTestHelper(
} }
} }
fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) { internal fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) {
Assert.assertNotNull(keys1) Assert.assertNotNull(keys1)
Assert.assertNotNull(keys2) Assert.assertNotNull(keys2)
@ -174,24 +162,27 @@ internal class KeysBackupTestHelper(
* - The new device must have the same count of megolm keys * - The new device must have the same count of megolm keys
* - Alice must have the same keys on both devices * - Alice must have the same keys on both devices
*/ */
fun checkRestoreSuccess( suspend fun checkRestoreSuccess(
testData: KeysBackupScenarioData, testData: KeysBackupScenarioData,
total: Int, total: Int,
imported: Int imported: Int
) { ) {
// - Imported keys number must be correct // - Imported keys number must be correct
Assert.assertEquals(testData.aliceKeys.size, total) Assert.assertEquals(testData.aliceKeysCount, total)
Assert.assertEquals(total, imported) Assert.assertEquals(total, imported)
// - The new device must have the same count of megolm keys // - The new device must have the same count of megolm keys
Assert.assertEquals(testData.aliceKeys.size, testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false)) val inboundGroupSessionCount = testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false)
Assert.assertEquals(testData.aliceKeysCount, inboundGroupSessionCount)
// - Alice must have the same keys on both devices // - Alice must have the same keys on both devices
for (aliceKey1 in testData.aliceKeys) { // TODO can't access internals as we can switch from rust/kotlin
val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store // for (aliceKey1 in testData.aliceKeys) {
.getInboundGroupSession(aliceKey1.safeSessionId!!, aliceKey1.senderKey!!) // val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
Assert.assertNotNull(aliceKey2) // .getInboundGroupSession(aliceKey1.safeSessionId!!, aliceKey1.senderKey!!)
assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys()) // Assert.assertNotNull(aliceKey2)
} // assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys())
// }
} }
} }

View File

@ -1,611 +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.verification
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.Session
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.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.SasMode
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart
import org.matrix.android.sdk.internal.crypto.model.rest.toValue
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Ignore
class SASTest : InstrumentedTest {
@Test
fun test_aliceStartThenAliceCancel() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService()
val bobTxCreatedLatch = CountDownLatch(1)
val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
bobTxCreatedLatch.countDown()
}
}
bobVerificationService.addListener(bobListener)
val txID = aliceVerificationService.beginKeyVerification(
VerificationMethod.SAS,
bobSession.myUserId,
bobSession.cryptoService().getMyDevice().deviceId,
null
)
assertNotNull("Alice should have a started transaction", txID)
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
assertNotNull("Alice should have a started transaction", aliceKeyTx)
testHelper.await(bobTxCreatedLatch)
bobVerificationService.removeListener(bobListener)
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
assertNotNull("Bob should have started verif transaction", bobKeyTx)
assertTrue(bobKeyTx is SASDefaultVerificationTransaction)
assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
assertTrue(aliceKeyTx is SASDefaultVerificationTransaction)
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
val aliceSasTx = aliceKeyTx as SASDefaultVerificationTransaction?
val bobSasTx = bobKeyTx as SASDefaultVerificationTransaction?
assertEquals("Alice state should be started", VerificationTxState.Started, aliceSasTx!!.state)
assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobSasTx!!.state)
// Let's cancel from alice side
val cancelLatch = CountDownLatch(1)
val bobListener2 = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
if (tx.transactionId == txID) {
val immutableState = (tx as SASDefaultVerificationTransaction).state
if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
cancelLatch.countDown()
}
}
}
}
bobVerificationService.addListener(bobListener2)
aliceSasTx.cancel(CancelCode.User)
testHelper.await(cancelLatch)
assertTrue("Should be cancelled on alice side", aliceSasTx.state is VerificationTxState.Cancelled)
assertTrue("Should be cancelled on bob side", bobSasTx.state is VerificationTxState.Cancelled)
val aliceCancelState = aliceSasTx.state as VerificationTxState.Cancelled
val bobCancelState = bobSasTx.state as VerificationTxState.Cancelled
assertTrue("Should be cancelled by me on alice side", aliceCancelState.byMe)
assertFalse("Should be cancelled by other on bob side", bobCancelState.byMe)
assertEquals("Should be User cancelled on alice side", CancelCode.User, aliceCancelState.cancelCode)
assertEquals("Should be User cancelled on bob side", CancelCode.User, bobCancelState.cancelCode)
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
}
@Test
@Ignore("This test will be ignored until it is fixed")
fun test_key_agreement_protocols_must_include_curve25519() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
fail("Not passing for the moment")
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
val protocols = listOf("meh_dont_know")
val tid = "00000000"
// Bob should receive a cancel
var cancelReason: CancelCode? = null
val cancelLatch = CountDownLatch(1)
val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
if (tx.transactionId == tid && tx.state is VerificationTxState.Cancelled) {
cancelReason = (tx.state as VerificationTxState.Cancelled).cancelCode
cancelLatch.countDown()
}
}
}
bobSession.cryptoService().verificationService().addListener(bobListener)
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
// TODO override fun onToDeviceEvent(event: Event?) {
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
// TODO canceledToDeviceEvent = event
// TODO cancelLatch.countDown()
// TODO }
// TODO }
// TODO }
// TODO })
val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
(tx as IncomingSasVerificationTransaction).performAccept()
}
}
}
aliceSession.cryptoService().verificationService().addListener(aliceListener)
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
testHelper.await(cancelLatch)
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
}
@Test
@Ignore("This test will be ignored until it is fixed")
fun test_key_agreement_macs_Must_include_hmac_sha256() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
fail("Not passing for the moment")
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
val mac = listOf("shaBit")
val tid = "00000000"
// Bob should receive a cancel
var canceledToDeviceEvent: Event? = null
val cancelLatch = CountDownLatch(1)
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
// TODO override fun onToDeviceEvent(event: Event?) {
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
// TODO canceledToDeviceEvent = event
// TODO cancelLatch.countDown()
// TODO }
// TODO }
// TODO }
// TODO })
val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
testHelper.await(cancelLatch)
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
}
@Test
@Ignore("This test will be ignored until it is fixed")
fun test_key_agreement_short_code_include_decimal() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
fail("Not passing for the moment")
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
val codes = listOf("bin", "foo", "bar")
val tid = "00000000"
// Bob should receive a cancel
var canceledToDeviceEvent: Event? = null
val cancelLatch = CountDownLatch(1)
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
// TODO override fun onToDeviceEvent(event: Event?) {
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
// TODO canceledToDeviceEvent = event
// TODO cancelLatch.countDown()
// TODO }
// TODO }
// TODO }
// TODO })
val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
testHelper.await(cancelLatch)
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
}
private fun fakeBobStart(
bobSession: Session,
aliceUserID: String?,
aliceDevice: String?,
tid: String,
protocols: List<String> = SASDefaultVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
hashes: List<String> = SASDefaultVerificationTransaction.KNOWN_HASHES,
mac: List<String> = SASDefaultVerificationTransaction.KNOWN_MACS,
codes: List<String> = SASDefaultVerificationTransaction.KNOWN_SHORT_CODES
) {
val startMessage = KeyVerificationStart(
fromDevice = bobSession.cryptoService().getMyDevice().deviceId,
method = VerificationMethod.SAS.toValue(),
transactionId = tid,
keyAgreementProtocols = protocols,
hashes = hashes,
messageAuthenticationCodes = mac,
shortAuthenticationStrings = codes
)
val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(aliceUserID, aliceDevice, startMessage)
// TODO val sendLatch = CountDownLatch(1)
// TODO bobSession.cryptoRestClient.sendToDevice(
// TODO EventType.KEY_VERIFICATION_START,
// TODO contentMap,
// TODO tid,
// TODO TestMatrixCallback<Void>(sendLatch)
// TODO )
}
// any two devices may only have at most one key verification in flight at a time.
// If a device has two verifications in progress with the same device, then it should cancel both verifications.
@Test
fun test_aliceStartTwoRequests() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val aliceCreatedLatch = CountDownLatch(2)
val aliceCancelledLatch = CountDownLatch(2)
val createdTx = mutableListOf<SASDefaultVerificationTransaction>()
val aliceListener = object : VerificationService.Listener {
override fun transactionCreated(tx: VerificationTransaction) {
createdTx.add(tx as SASDefaultVerificationTransaction)
aliceCreatedLatch.countDown()
}
override fun transactionUpdated(tx: VerificationTransaction) {
if ((tx as SASDefaultVerificationTransaction).state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) {
aliceCancelledLatch.countDown()
}
}
}
aliceVerificationService.addListener(aliceListener)
val bobUserId = bobSession!!.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
testHelper.await(aliceCreatedLatch)
testHelper.await(aliceCancelledLatch)
cryptoTestData.cleanUp(testHelper)
}
/**
* Test that when alice starts a 'correct' request, bob agrees.
*/
@Test
@Ignore("This test will be ignored until it is fixed")
fun test_aliceAndBobAgreement() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService()
var accepted: ValidVerificationInfoAccept? = null
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
val aliceAcceptedLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
Log.v("TEST", "== aliceTx state ${tx.state} => ${(tx as? OutgoingSasVerificationTransaction)?.uxState}")
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
val at = tx as SASDefaultVerificationTransaction
accepted = at.accepted
startReq = at.startReq
aliceAcceptedLatch.countDown()
}
}
}
aliceVerificationService.addListener(aliceListener)
val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
Log.v("TEST", "== bobTx state ${tx.state} => ${(tx as? IncomingSasVerificationTransaction)?.uxState}")
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
bobVerificationService.removeListener(this)
val at = tx as IncomingSasVerificationTransaction
at.performAccept()
}
}
}
bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
testHelper.await(aliceAcceptedLatch)
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
// check that agreement is valid
assertTrue("Agreed Protocol should be Valid", accepted != null)
assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols.contains(accepted!!.keyAgreementProtocol))
assertTrue("Hash should be known by alice", startReq!!.hashes.contains(accepted!!.hash))
assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes.contains(accepted!!.messageAuthenticationCode))
accepted!!.shortAuthenticationStrings.forEach {
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
}
}
@Test
fun test_aliceAndBobSASCode() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService()
val aliceSASLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
when (uxState) {
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
aliceSASLatch.countDown()
}
else -> Unit
}
}
}
aliceVerificationService.addListener(aliceListener)
val bobSASLatch = CountDownLatch(1)
val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as IncomingSasVerificationTransaction).uxState
when (uxState) {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
tx.performAccept()
}
else -> Unit
}
if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) {
bobSASLatch.countDown()
}
}
}
bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
testHelper.await(aliceSASLatch)
testHelper.await(bobSASLatch)
val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
assertEquals(
"Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
bobTx.getShortCodeRepresentation(SasMode.DECIMAL)
)
}
@Test
fun test_happyPath() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService()
val aliceSASLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.Listener {
var matchOnce = true
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
Log.v("TEST", "== aliceState ${uxState.name}")
when (uxState) {
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
tx.userHasVerifiedShortCode()
}
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
if (matchOnce) {
matchOnce = false
aliceSASLatch.countDown()
}
}
else -> Unit
}
}
}
aliceVerificationService.addListener(aliceListener)
val bobSASLatch = CountDownLatch(1)
val bobListener = object : VerificationService.Listener {
var acceptOnce = true
var matchOnce = true
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as IncomingSasVerificationTransaction).uxState
Log.v("TEST", "== bobState ${uxState.name}")
when (uxState) {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
if (acceptOnce) {
acceptOnce = false
tx.performAccept()
}
}
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
if (matchOnce) {
matchOnce = false
tx.userHasVerifiedShortCode()
}
}
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
bobSASLatch.countDown()
}
else -> Unit
}
}
}
bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
testHelper.await(aliceSASLatch)
testHelper.await(bobSASLatch)
// Assert that devices are verified
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId)
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? =
bobSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId)
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
}
@Test
fun test_ConcurrentStart() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService()
val req = aliceVerificationService.requestKeyVerificationInDMs(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
bobSession.myUserId,
cryptoTestData.roomId
)
var requestID: String? = null
testHelper.retryPeriodically {
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
requestID = prAlicePOV?.transactionId
Log.v("TEST", "== alicePOV is $prAlicePOV")
prAlicePOV?.transactionId != null && prAlicePOV.localId == req.localId
}
Log.v("TEST", "== requestID is $requestID")
testHelper.retryPeriodically {
val prBobPOV = bobVerificationService.getExistingVerificationRequests(aliceSession.myUserId).firstOrNull()
Log.v("TEST", "== prBobPOV is $prBobPOV")
prBobPOV?.transactionId == requestID
}
bobVerificationService.readyPendingVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
aliceSession.myUserId,
requestID!!
)
// wait for alice to get the ready
testHelper.retryPeriodically {
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
Log.v("TEST", "== prAlicePOV is $prAlicePOV")
prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null
}
// Start concurrent!
aliceVerificationService.beginKeyVerificationInDMs(
VerificationMethod.SAS,
requestID!!,
cryptoTestData.roomId,
bobSession.myUserId,
bobSession.sessionParams.deviceId!!
)
bobVerificationService.beginKeyVerificationInDMs(
VerificationMethod.SAS,
requestID!!,
cryptoTestData.roomId,
aliceSession.myUserId,
aliceSession.sessionParams.deviceId!!
)
// we should reach SHOW SAS on both
var alicePovTx: SasVerificationTransaction?
var bobPovTx: SasVerificationTransaction?
testHelper.retryPeriodically {
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID!!) as? SasVerificationTransaction
Log.v("TEST", "== alicePovTx is $alicePovTx")
alicePovTx?.state == VerificationTxState.ShortCodeReady
}
// wait for alice to get the ready
testHelper.retryPeriodically {
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID!!) as? SasVerificationTransaction
Log.v("TEST", "== bobPovTx is $bobPovTx")
bobPovTx?.state == VerificationTxState.ShortCodeReady
}
}
}

View File

@ -0,0 +1,101 @@
/*
* 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.verification
import org.amshove.kluent.fail
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestData
class SasVerificationTestHelper(private val testHelper: CommonTestHelper) {
suspend fun requestVerificationAndWaitForReadyState(cryptoTestData: CryptoTestData, supportedMethods: List<VerificationMethod>): String {
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession.cryptoService().verificationService()
val bobUserId = bobSession.myUserId
// Step 1: Alice starts a verification request
val transactionId = aliceVerificationService.requestKeyVerificationInDMs(
supportedMethods, bobUserId, cryptoTestData.roomId
)
.transactionId
testHelper.retryWithBackoff(
onFail = {
fail("bob should see an incoming verification request with id $transactionId")
}
) {
val incomingRequest = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, transactionId)
if (incomingRequest != null) {
bobVerificationService.readyPendingVerification(
supportedMethods,
aliceSession.myUserId,
incomingRequest.transactionId
)
true
} else {
false
}
}
// wait for alice to see the ready
testHelper.retryWithBackoff(
onFail = {
fail("Alice request whould be ready $transactionId")
}
) {
val pendingRequest = aliceVerificationService.getExistingVerificationRequest(bobUserId, transactionId)
pendingRequest?.state == EVerificationState.Ready
}
return transactionId
}
suspend fun requestSelfKeyAndWaitForReadyState(session1: Session, session2: Session, supportedMethods: List<VerificationMethod>): String {
val session1VerificationService = session1.cryptoService().verificationService()
val session2VerificationService = session2.cryptoService().verificationService()
val requestID = session1VerificationService.requestSelfKeyVerification(supportedMethods).transactionId
val myUserId = session1.myUserId
testHelper.retryWithBackoff {
val incomingRequest = session2VerificationService.getExistingVerificationRequest(myUserId, requestID)
if (incomingRequest != null) {
session2VerificationService.readyPendingVerification(
supportedMethods,
myUserId,
incomingRequest.transactionId
)
true
} else {
false
}
}
// wait for alice to see the ready
testHelper.retryPeriodically {
val pendingRequest = session1VerificationService.getExistingVerificationRequest(myUserId, requestID)
pendingRequest?.state == EVerificationState.Ready
}
return requestID
}
}

View File

@ -0,0 +1,199 @@
/*
* 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.verification
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.amshove.kluent.shouldBe
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class VerificationTest : InstrumentedTest {
data class ExpectedResult(
val sasIsSupported: Boolean = false,
val otherCanScanQrCode: Boolean = false,
val otherCanShowQrCode: Boolean = false
)
private val sas = listOf(
VerificationMethod.SAS
)
private val sasShow = listOf(
VerificationMethod.SAS,
VerificationMethod.QR_CODE_SHOW
)
private val sasScan = listOf(
VerificationMethod.SAS,
VerificationMethod.QR_CODE_SCAN
)
private val sasShowScan = listOf(
VerificationMethod.SAS,
VerificationMethod.QR_CODE_SHOW,
VerificationMethod.QR_CODE_SCAN
)
@Test
fun test_aliceAndBob_sas_sas() = doTest(
sas,
sas,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_sas_show() = doTest(
sas,
sasShow,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_show_sas() = doTest(
sasShow,
sas,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_sas_scan() = doTest(
sas,
sasScan,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_scan_sas() = doTest(
sasScan,
sas,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_scan_scan() = doTest(
sasScan,
sasScan,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_show_show() = doTest(
sasShow,
sasShow,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_show_scan() = doTest(
sasShow,
sasScan,
ExpectedResult(sasIsSupported = true, otherCanScanQrCode = true),
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true)
)
@Test
fun test_aliceAndBob_scan_show() = doTest(
sasScan,
sasShow,
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true),
ExpectedResult(sasIsSupported = true, otherCanScanQrCode = true)
)
@Test
fun test_aliceAndBob_all_all() = doTest(
sasShowScan,
sasShowScan,
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true),
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true)
)
private fun doTest(
aliceSupportedMethods: List<VerificationMethod>,
bobSupportedMethods: List<VerificationMethod>,
expectedResultForAlice: ExpectedResult,
expectedResultForBob: ExpectedResult
) = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
cryptoTestHelper.initializeCrossSigning(aliceSession)
cryptoTestHelper.initializeCrossSigning(bobSession)
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession.cryptoService().verificationService()
val transactionId = aliceVerificationService.requestKeyVerificationInDMs(
aliceSupportedMethods, bobSession.myUserId, cryptoTestData.roomId
)
.transactionId
testHelper.retryPeriodically {
val incomingRequest = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, transactionId)
if (incomingRequest != null) {
bobVerificationService.readyPendingVerification(
bobSupportedMethods,
aliceSession.myUserId,
incomingRequest.transactionId
)
true
} else {
false
}
}
// wait for alice to see the ready
testHelper.retryPeriodically {
val pendingRequest = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId, transactionId)
pendingRequest?.state == EVerificationState.Ready
}
val aliceReadyPendingVerificationRequest = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId, transactionId)!!
val bobReadyPendingVerificationRequest = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, transactionId)!!
aliceReadyPendingVerificationRequest.let { pr ->
pr.isSasSupported shouldBe expectedResultForAlice.sasIsSupported
pr.weShouldShowScanOption shouldBe expectedResultForAlice.otherCanShowQrCode
pr.weShouldDisplayQRCode shouldBe expectedResultForAlice.otherCanScanQrCode
}
bobReadyPendingVerificationRequest.let { pr ->
pr.isSasSupported shouldBe expectedResultForBob.sasIsSupported
pr.weShouldShowScanOption shouldBe expectedResultForBob.otherCanShowQrCode
pr.weShouldDisplayQRCode shouldBe expectedResultForBob.otherCanScanQrCode
}
cryptoTestData.cleanUp(testHelper)
}
}

View File

@ -1,46 +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.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.amshove.kluent.shouldBe
import org.amshove.kluent.shouldNotBeEqualTo
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class SharedSecretTest : InstrumentedTest {
@Test
fun testSharedSecretLengthCase() {
repeat(100) {
generateSharedSecretV2().length shouldBe 11
}
}
@Test
fun testSharedDiffCase() {
val sharedSecret1 = generateSharedSecretV2()
val sharedSecret2 = generateSharedSecretV2()
sharedSecret1 shouldNotBeEqualTo sharedSecret2
}
}

View File

@ -17,6 +17,8 @@
package org.matrix.android.sdk.internal.crypto.verification.qrcode package org.matrix.android.sdk.internal.crypto.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import org.amshove.kluent.shouldBe import org.amshove.kluent.shouldBe
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Ignore import org.junit.Ignore
@ -29,14 +31,13 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import org.matrix.android.sdk.api.session.crypto.verification.VerificationEvent
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.SessionTestParams
import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.common.TestConstants
import java.util.concurrent.CountDownLatch
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -164,7 +165,6 @@ class VerificationTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!! val bobSession = cryptoTestData.secondSession!!
testHelper.waitForCallback<Unit> { callback ->
aliceSession.cryptoService().crossSigningService() aliceSession.cryptoService().crossSigningService()
.initializeCrossSigning( .initializeCrossSigning(
object : UserInteractiveAuthInterceptor { object : UserInteractiveAuthInterceptor {
@ -177,11 +177,9 @@ class VerificationTest : InstrumentedTest {
) )
) )
} }
}, callback
)
} }
)
testHelper.waitForCallback<Unit> { callback ->
bobSession.cryptoService().crossSigningService() bobSession.cryptoService().crossSigningService()
.initializeCrossSigning( .initializeCrossSigning(
object : UserInteractiveAuthInterceptor { object : UserInteractiveAuthInterceptor {
@ -194,64 +192,50 @@ class VerificationTest : InstrumentedTest {
) )
) )
} }
}, callback
)
} }
)
val aliceVerificationService = aliceSession.cryptoService().verificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession.cryptoService().verificationService() val bobVerificationService = bobSession.cryptoService().verificationService()
var aliceReadyPendingVerificationRequest: PendingVerificationRequest? = null val transactionId = aliceVerificationService.requestKeyVerificationInDMs(
var bobReadyPendingVerificationRequest: PendingVerificationRequest? = null aliceSupportedMethods, bobSession.myUserId, cryptoTestData.roomId
)
.transactionId
val latch = CountDownLatch(2) testHelper.retryPeriodically {
val aliceListener = object : VerificationService.Listener { val incomingRequest = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, transactionId)
override fun verificationRequestUpdated(pr: PendingVerificationRequest) { if (incomingRequest != null) {
// Step 4: Alice receive the ready request bobVerificationService.readyPendingVerification(
if (pr.isReady) {
aliceReadyPendingVerificationRequest = pr
latch.countDown()
}
}
}
aliceVerificationService.addListener(aliceListener)
val bobListener = object : VerificationService.Listener {
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
// Step 2: Bob accepts the verification request
bobVerificationService.readyPendingVerificationInDMs(
bobSupportedMethods, bobSupportedMethods,
aliceSession.myUserId, aliceSession.myUserId,
cryptoTestData.roomId, incomingRequest.transactionId
pr.transactionId!!
) )
true
} else {
false
}
} }
override fun verificationRequestUpdated(pr: PendingVerificationRequest) { // wait for alice to see the ready
// Step 3: Bob is ready testHelper.retryPeriodically {
if (pr.isReady) { val pendingRequest = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId, transactionId)
bobReadyPendingVerificationRequest = pr pendingRequest?.state == EVerificationState.Ready
latch.countDown()
}
}
}
bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId
// Step 1: Alice starts a verification request
aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId)
testHelper.await(latch)
aliceReadyPendingVerificationRequest!!.let { pr ->
pr.isSasSupported() shouldBe expectedResultForAlice.sasIsSupported
pr.otherCanShowQrCode() shouldBe expectedResultForAlice.otherCanShowQrCode
pr.otherCanScanQrCode() shouldBe expectedResultForAlice.otherCanScanQrCode
} }
bobReadyPendingVerificationRequest!!.let { pr -> val aliceReadyPendingVerificationRequest = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId, transactionId)!!
pr.isSasSupported() shouldBe expectedResultForBob.sasIsSupported val bobReadyPendingVerificationRequest = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, transactionId)!!
pr.otherCanShowQrCode() shouldBe expectedResultForBob.otherCanShowQrCode
pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode aliceReadyPendingVerificationRequest.let { pr ->
pr.isSasSupported shouldBe expectedResultForAlice.sasIsSupported
pr.weShouldShowScanOption shouldBe expectedResultForAlice.otherCanShowQrCode
pr.weShouldDisplayQRCode shouldBe expectedResultForAlice.otherCanScanQrCode
}
bobReadyPendingVerificationRequest.let { pr ->
pr.isSasSupported shouldBe expectedResultForBob.sasIsSupported
pr.weShouldShowScanOption shouldBe expectedResultForBob.otherCanShowQrCode
pr.weShouldDisplayQRCode shouldBe expectedResultForBob.otherCanScanQrCode
} }
} }
@ -273,21 +257,42 @@ class VerificationTest : InstrumentedTest {
val serviceOfVerifier = aliceSessionThatVerifies.cryptoService().verificationService() val serviceOfVerifier = aliceSessionThatVerifies.cryptoService().verificationService()
val serviceOfUserWhoReceivesCancellation = aliceSessionThatReceivesCanceledEvent.cryptoService().verificationService() val serviceOfUserWhoReceivesCancellation = aliceSessionThatReceivesCanceledEvent.cryptoService().verificationService()
serviceOfVerifier.addListener(object : VerificationService.Listener { var job: Job? = null
override fun verificationRequestCreated(pr: PendingVerificationRequest) { job = async {
// Accept verification request serviceOfVerifier.requestEventFlow().collect {
when (it) {
is VerificationEvent.RequestAdded -> {
val pr = it.request
serviceOfVerifier.readyPendingVerification( serviceOfVerifier.readyPendingVerification(
verificationMethods, verificationMethods,
pr.otherUserId, pr.otherUserId,
pr.transactionId!!, pr.transactionId,
) )
job?.cancel()
} }
}) is VerificationEvent.RequestUpdated,
is VerificationEvent.TransactionAdded,
is VerificationEvent.TransactionUpdated -> {
}
}
}
}
job.await()
// serviceOfVerifier.addListener(object : VerificationService.Listener {
// override fun verificationRequestCreated(pr: PendingVerificationRequest) {
// // Accept verification request
// runBlocking {
// serviceOfVerifier.readyPendingVerification(
// verificationMethods,
// pr.otherUserId,
// pr.transactionId!!,
// )
// }
// }
// })
serviceOfVerified.requestKeyVerification( serviceOfVerified.requestSelfKeyVerification(
methods = verificationMethods, methods = verificationMethods,
otherUserId = aliceSessionToVerify.myUserId,
otherDevices = listOfNotNull(aliceSessionThatVerifies.sessionParams.deviceId, aliceSessionThatReceivesCanceledEvent.sessionParams.deviceId),
) )
testHelper.retryPeriodically { testHelper.retryPeriodically {

View File

@ -52,9 +52,7 @@ class PreShareKeysTest : InstrumentedTest {
Log.d("#Test", "Room Key Received from alice $preShareCount") Log.d("#Test", "Room Key Received from alice $preShareCount")
// Force presharing of new outbound key // Force presharing of new outbound key
testHelper.waitForCallback<Unit> { aliceSession.cryptoService().prepareToEncrypt(e2eRoomID)
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID, it)
}
testHelper.retryPeriodically { testHelper.retryPeriodically {
val newKeysCount = bobSession.cryptoService().keysBackupService().getTotalNumbersOfKeys() val newKeysCount = bobSession.cryptoService().keysBackupService().getTotalNumbersOfKeys()
@ -65,7 +63,7 @@ class PreShareKeysTest : InstrumentedTest {
val aliceOutboundSessionInRoom = aliceCryptoStore.getCurrentOutboundGroupSessionForRoom(e2eRoomID)!!.outboundGroupSession.sessionIdentifier() val aliceOutboundSessionInRoom = aliceCryptoStore.getCurrentOutboundGroupSessionForRoom(e2eRoomID)!!.outboundGroupSession.sessionIdentifier()
val bobCryptoStore = (bobSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting val bobCryptoStore = (bobSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
val aliceDeviceBobPov = bobCryptoStore.getUserDevice(aliceSession.myUserId, aliceSession.sessionParams.deviceId!!)!! val aliceDeviceBobPov = bobCryptoStore.getUserDevice(aliceSession.myUserId, aliceSession.sessionParams.deviceId)!!
val bobInboundForAlice = bobCryptoStore.getInboundGroupSession(aliceOutboundSessionInRoom, aliceDeviceBobPov.identityKey()!!) val bobInboundForAlice = bobCryptoStore.getInboundGroupSession(aliceOutboundSessionInRoom, aliceDeviceBobPov.identityKey()!!)
assertNotNull("Bob should have received and decrypted a room key event from alice", bobInboundForAlice) assertNotNull("Bob should have received and decrypted a room key event from alice", bobInboundForAlice)
assertEquals("Wrong room", e2eRoomID, bobInboundForAlice!!.roomId) assertEquals("Wrong room", e2eRoomID, bobInboundForAlice!!.roomId)

View File

@ -94,7 +94,9 @@ class UnwedgingTest : InstrumentedTest {
val bobSession = cryptoTestData.secondSession!! val bobSession = cryptoTestData.secondSession!!
val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
val olmDevice = (aliceSession.cryptoService() as DefaultCryptoService).olmDeviceForTest
// bobSession.cryptoService().setWarnOnUnknownDevices(false)
// aliceSession.cryptoService().setWarnOnUnknownDevices(false)
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
@ -116,9 +118,9 @@ class UnwedgingTest : InstrumentedTest {
// - Store the olm session between A&B devices // - Store the olm session between A&B devices
// Let us pickle our session with bob here so we can later unpickle it // Let us pickle our session with bob here so we can later unpickle it
// and wedge our session. // and wedge our session.
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(bobSession.cryptoService().getMyDevice().identityKey()!!) val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(bobSession.cryptoService().getMyCryptoDevice().identityKey()!!)
sessionIdsForBob!!.size shouldBeEqualTo 1 sessionIdsForBob!!.size shouldBeEqualTo 1
val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), bobSession.cryptoService().getMyDevice().identityKey()!!)!! val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), bobSession.cryptoService().getMyCryptoDevice().identityKey()!!)!!
val oldSession = serializeForRealm(olmSession.olmSession) val oldSession = serializeForRealm(olmSession.olmSession)
@ -142,9 +144,10 @@ class UnwedgingTest : InstrumentedTest {
aliceCryptoStore.storeSession( aliceCryptoStore.storeSession(
OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!), OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!),
bobSession.cryptoService().getMyDevice().identityKey()!! bobSession.cryptoService().getMyCryptoDevice().identityKey()!!
) )
olmDevice.clearOlmSessionCache() // TODO mmm we can't do that with rust
// olmDevice.clearOlmSessionCache()
// Force new session, and key share // Force new session, and key share
aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId) aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId)
@ -170,7 +173,6 @@ class UnwedgingTest : InstrumentedTest {
Assert.assertTrue(messagesReceivedByBob[0].root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) Assert.assertTrue(messagesReceivedByBob[0].root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
// It's a trick to force key request on fail to decrypt // It's a trick to force key request on fail to decrypt
testHelper.waitForCallback<Unit> {
bobSession.cryptoService().crossSigningService() bobSession.cryptoService().crossSigningService()
.initializeCrossSigning( .initializeCrossSigning(
object : UserInteractiveAuthInterceptor { object : UserInteractiveAuthInterceptor {
@ -183,9 +185,7 @@ class UnwedgingTest : InstrumentedTest {
) )
) )
} }
}, it })
)
}
// Wait until we received back the key // Wait until we received back the key
testHelper.retryPeriodically { testHelper.retryPeriodically {

View File

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import org.amshove.kluent.internal.assertFailsWith
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
@ -30,19 +31,13 @@ import org.junit.runner.RunWith
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.listeners.StepProgressListener import org.matrix.android.sdk.api.listeners.StepProgressListener
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupUtils
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrustSignature import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrustSignature
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
@ -50,7 +45,6 @@ import org.matrix.android.sdk.common.RetryTestRule
import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.common.TestConstants
import org.matrix.android.sdk.common.waitFor import org.matrix.android.sdk.common.waitFor
import java.security.InvalidParameterException import java.security.InvalidParameterException
import java.util.Collections
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -83,7 +77,7 @@ class KeysBackupTest : InstrumentedTest {
// - Check backup keys after having marked one as backed up // - Check backup keys after having marked one as backed up
val session = sessions[0] val session = sessions[0]
cryptoStore.markBackupDoneForInboundGroupSessions(Collections.singletonList(session)) cryptoStore.markBackupDoneForInboundGroupSessions(listOf(session))
assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)) assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
assertEquals(1, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)) assertEquals(1, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
@ -118,9 +112,7 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled()) assertFalse(keysBackup.isEnabled())
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> { val megolmBackupCreationInfo = keysBackup.prepareKeysBackupVersion(null, null)
keysBackup.prepareKeysBackupVersion(null, null, it)
}
assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, megolmBackupCreationInfo.algorithm) assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, megolmBackupCreationInfo.algorithm)
assertNotNull(megolmBackupCreationInfo.authData.publicKey) assertNotNull(megolmBackupCreationInfo.authData.publicKey)
@ -144,27 +136,20 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled()) assertFalse(keysBackup.isEnabled())
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> { val megolmBackupCreationInfo =
keysBackup.prepareKeysBackupVersion(null, null, it) keysBackup.prepareKeysBackupVersion(null, null)
}
assertFalse(keysBackup.isEnabled()) assertFalse(keysBackup.isEnabled())
// Create the version // Create the version
val version = testHelper.waitForCallback<KeysVersion> { val version = keysBackup.createKeysBackupVersion(megolmBackupCreationInfo)
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
}
// Backup must be enable now // Backup must be enable now
assertTrue(keysBackup.isEnabled()) assertTrue(keysBackup.isEnabled())
// Check that it's signed with MSK // Check that it's signed with MSK
val versionResult = testHelper.waitForCallback<KeysVersionResult?> { val versionResult = keysBackup.getVersion(version.version)
keysBackup.getVersion(version.version, it) val trust = keysBackup.getKeysBackupTrust(versionResult!!)
}
val trust = testHelper.waitForCallback<KeysBackupVersionTrust> {
keysBackup.getKeysBackupTrust(versionResult!!, it)
}
assertEquals("Should have 2 signatures", 2, trust.signatures.size) assertEquals("Should have 2 signatures", 2, trust.signatures.size)
@ -211,7 +196,6 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(2, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)) assertEquals(2, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)) assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
val stateObserver = StateObserver(keysBackup, latch, 5) val stateObserver = StateObserver(keysBackup, latch, 5)
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
@ -256,18 +240,9 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(2, nbOfKeys) assertEquals(2, nbOfKeys)
var lastBackedUpKeysProgress = 0 testHelper.retryPeriodically {
keysBackup.getTotalNumbersOfKeys() == keysBackup.getTotalNumbersOfBackedUpKeys()
testHelper.waitForCallback<Unit> {
keysBackup.backupAllGroupSessions(object : ProgressListener {
override fun onProgress(progress: Int, total: Int) {
assertEquals(nbOfKeys, total)
lastBackedUpKeysProgress = progress
} }
}, it)
}
assertEquals(nbOfKeys, lastBackedUpKeysProgress)
val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true) val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
@ -305,7 +280,7 @@ class KeysBackupTest : InstrumentedTest {
assertNotNull(keyBackupData!!.sessionData) assertNotNull(keyBackupData!!.sessionData)
// - Check pkDecryptionFromRecoveryKey() is able to create a OlmPkDecryption // - Check pkDecryptionFromRecoveryKey() is able to create a OlmPkDecryption
val decryption = keysBackup.pkDecryptionFromRecoveryKey(keyBackupCreationInfo.recoveryKey) val decryption = keysBackup.pkDecryptionFromRecoveryKey(keyBackupCreationInfo.recoveryKey.toBase58())
assertNotNull(decryption) assertNotNull(decryption)
// - Check decryptKeyBackupData() returns stg // - Check decryptKeyBackupData() returns stg
val sessionData = keysBackup val sessionData = keysBackup
@ -313,7 +288,7 @@ class KeysBackupTest : InstrumentedTest {
keyBackupData, keyBackupData,
session.safeSessionId!!, session.safeSessionId!!,
cryptoTestData.roomId, cryptoTestData.roomId,
decryption!! keyBackupCreationInfo.recoveryKey
) )
assertNotNull(sessionData) assertNotNull(sessionData)
// - Compare the decrypted megolm key with the original one // - Compare the decrypted megolm key with the original one
@ -335,16 +310,13 @@ class KeysBackupTest : InstrumentedTest {
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
// - Restore the e2e backup from the homeserver // - Restore the e2e backup from the homeserver
val importRoomKeysResult = testHelper.waitForCallback<ImportRoomKeysResult> { val importRoomKeysResult = testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
null, null,
null, null,
null, null
it
) )
}
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
@ -401,7 +373,7 @@ class KeysBackupTest : InstrumentedTest {
// // Request is either sent or unsent // // Request is either sent or unsent
// assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null) // assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null)
// //
// testData.cleanUp(mTestHelper) // testData.cleanUp(testHelper)
// } // }
/** /**
@ -430,13 +402,10 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
// - Trust the backup from the new device // - Trust the backup from the new device
testHelper.waitForCallback<Unit> {
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion( testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
true, true
it
) )
}
// Wait for backup state to be ReadyToBackUp // Wait for backup state to be ReadyToBackUp
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
@ -446,16 +415,17 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
// - Retrieve the last version from the server // - Retrieve the last version from the server
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> { val keysVersionResult = testData.aliceSession2.cryptoService()
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) .keysBackupService()
}.toKeysVersionResult() .getCurrentVersion()!!
.toKeysVersionResult()
// - It must be the same // - It must be the same
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> { val keysBackupVersionTrust = testData.aliceSession2.cryptoService()
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) .keysBackupService()
} .getKeysBackupTrust(keysVersionResult)
// - It must be trusted and must have 2 signatures now // - It must be trusted and must have 2 signatures now
assertTrue(keysBackupVersionTrust.usable) assertTrue(keysBackupVersionTrust.usable)
@ -490,32 +460,32 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
// - Trust the backup from the new device with the recovery key // - Trust the backup from the new device with the recovery key
testHelper.waitForCallback<Unit> {
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey( testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey
it
) )
}
// Wait for backup state to be ReadyToBackUp // Wait for backup state to be ReadyToBackUp
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
// - Backup must be enabled on the new device, on the same version // - Backup must be enabled on the new device, on the same version
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version) assertEquals(
testData.prepareKeysBackupDataResult.version,
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version
)
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
// - Retrieve the last version from the server // - Retrieve the last version from the server
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> { val keysVersionResult = testData.aliceSession2.cryptoService().keysBackupService()
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) .getCurrentVersion()!!
}.toKeysVersionResult() .toKeysVersionResult()
// - It must be the same // - It must be the same
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> { val keysBackupVersionTrust = testData.aliceSession2.cryptoService()
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) .keysBackupService()
} .getKeysBackupTrust(keysVersionResult)
// - It must be trusted and must have 2 signatures now // - It must be trusted and must have 2 signatures now
assertTrue(keysBackupVersionTrust.usable) assertTrue(keysBackupVersionTrust.usable)
@ -548,13 +518,10 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
// - Try to trust the backup from the new device with a wrong recovery key // - Try to trust the backup from the new device with a wrong recovery key
testHelper.waitForCallbackError<Unit> {
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey( testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
"Bad recovery key", BackupUtils.recoveryKeyFromPassphrase("Bad recovery key")!!,
it
) )
}
// - The new device must still see the previous backup as not trusted // - The new device must still see the previous backup as not trusted
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
@ -592,13 +559,10 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
// - Trust the backup from the new device with the password // - Trust the backup from the new device with the password
testHelper.waitForCallback<Unit> {
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase( testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
password, password
it
) )
}
// Wait for backup state to be ReadyToBackUp // Wait for backup state to be ReadyToBackUp
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
@ -608,16 +572,16 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
// - Retrieve the last version from the server // - Retrieve the last version from the server
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> { val keysVersionResult = testData.aliceSession2.cryptoService().keysBackupService()
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) .getCurrentVersion()!!
}.toKeysVersionResult() .toKeysVersionResult()
// - It must be the same // - It must be the same
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> { val keysBackupVersionTrust = testData.aliceSession2.cryptoService()
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) .keysBackupService()
} .getKeysBackupTrust(keysVersionResult)
// - It must be trusted and must have 2 signatures now // - It must be trusted and must have 2 signatures now
assertTrue(keysBackupVersionTrust.usable) assertTrue(keysBackupVersionTrust.usable)
@ -653,13 +617,10 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
// - Try to trust the backup from the new device with a wrong password // - Try to trust the backup from the new device with a wrong password
testHelper.waitForCallbackError<Unit> {
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase( testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
badPassword, badPassword,
it
) )
}
// - The new device must still see the previous backup as not trusted // - The new device must still see the previous backup as not trusted
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
@ -683,18 +644,15 @@ class KeysBackupTest : InstrumentedTest {
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService() val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
// - Try to restore the e2e backup with a wrong recovery key // - Try to restore the e2e backup with a wrong recovery key
val importRoomKeysResult = testHelper.waitForCallbackError<ImportRoomKeysResult> { assertFailsWith<InvalidParameterException> {
keysBackupService.restoreKeysWithRecoveryKey( keysBackupService.restoreKeysWithRecoveryKey(
keysBackupService.keysBackupVersion!!, keysBackupService.keysBackupVersion!!,
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d", BackupUtils.recoveryKeyFromBase58("EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d")!!,
null, null,
null, null,
null, null,
it
) )
} }
assertTrue(importRoomKeysResult is InvalidParameterException)
} }
/** /**
@ -714,8 +672,7 @@ class KeysBackupTest : InstrumentedTest {
// - Restore the e2e backup with the password // - Restore the e2e backup with the password
val steps = ArrayList<StepProgressListener.Step>() val steps = ArrayList<StepProgressListener.Step>()
val importRoomKeysResult = testHelper.waitForCallback<ImportRoomKeysResult> { val importRoomKeysResult = testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(
testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
password, password,
null, null,
@ -724,10 +681,8 @@ class KeysBackupTest : InstrumentedTest {
override fun onStepProgress(step: StepProgressListener.Step) { override fun onStepProgress(step: StepProgressListener.Step) {
steps.add(step) steps.add(step)
} }
},
it
)
} }
)
// Check steps // Check steps
assertEquals(105, steps.size) assertEquals(105, steps.size)
@ -770,18 +725,15 @@ class KeysBackupTest : InstrumentedTest {
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService() val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
// - Try to restore the e2e backup with a wrong password // - Try to restore the e2e backup with a wrong password
val importRoomKeysResult = testHelper.waitForCallbackError<ImportRoomKeysResult> { assertFailsWith<InvalidParameterException> {
keysBackupService.restoreKeyBackupWithPassword( keysBackupService.restoreKeyBackupWithPassword(
keysBackupService.keysBackupVersion!!, keysBackupService.keysBackupVersion!!,
wrongPassword, wrongPassword,
null, null,
null, null,
null, null,
it
) )
} }
assertTrue(importRoomKeysResult is InvalidParameterException)
} }
/** /**
@ -799,16 +751,13 @@ class KeysBackupTest : InstrumentedTest {
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
// - Restore the e2e backup with the recovery key. // - Restore the e2e backup with the recovery key.
val importRoomKeysResult = testHelper.waitForCallback<ImportRoomKeysResult> { val importRoomKeysResult = testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
null, null,
null, null,
null, null
it
) )
}
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
} }
@ -827,18 +776,15 @@ class KeysBackupTest : InstrumentedTest {
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService() val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
// - Try to restore the e2e backup with a password // - Try to restore the e2e backup with a password
val importRoomKeysResult = testHelper.waitForCallbackError<ImportRoomKeysResult> { val importRoomKeysResult = keysBackupService.restoreKeyBackupWithPassword(
keysBackupService.restoreKeyBackupWithPassword(
keysBackupService.keysBackupVersion!!, keysBackupService.keysBackupVersion!!,
"password", "password",
null, null,
null, null,
null, null,
it
) )
}
assertTrue(importRoomKeysResult is IllegalStateException) assertTrue(importRoomKeysResult.importedSessionInfo.size > 0)
} }
/** /**
@ -860,14 +806,10 @@ class KeysBackupTest : InstrumentedTest {
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
// Get key backup version from the homeserver // Get key backup version from the homeserver
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> { val keysVersionResult = keysBackup.getCurrentVersion()!!.toKeysVersionResult()
keysBackup.getCurrentVersion(it)
}.toKeysVersionResult()
// - Check the returned KeyBackupVersion is trusted // - Check the returned KeyBackupVersion is trusted
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> { val keysBackupVersionTrust = keysBackup.getKeysBackupTrust(keysVersionResult!!)
keysBackup.getKeysBackupTrust(keysVersionResult!!, it)
}
assertNotNull(keysBackupVersionTrust) assertNotNull(keysBackupVersionTrust)
assertTrue(keysBackupVersionTrust.usable) assertTrue(keysBackupVersionTrust.usable)
@ -876,7 +818,7 @@ class KeysBackupTest : InstrumentedTest {
val signature = keysBackupVersionTrust.signatures[0] as KeysBackupVersionTrustSignature.DeviceSignature val signature = keysBackupVersionTrust.signatures[0] as KeysBackupVersionTrustSignature.DeviceSignature
assertTrue(signature.valid) assertTrue(signature.valid)
assertNotNull(signature.device) assertNotNull(signature.device)
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId) assertEquals(cryptoTestData.firstSession.cryptoService().getMyCryptoDevice().deviceId, signature.deviceId)
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId) assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
@ -944,7 +886,9 @@ class KeysBackupTest : InstrumentedTest {
(cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store.resetBackupMarkers() (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store.resetBackupMarkers()
// - Make alice back up all her keys again // - Make alice back up all her keys again
testHelper.waitForCallbackError<Unit> { keysBackup.backupAllGroupSessions(null, it) } testHelper.retryPeriodically {
keysBackup.getTotalNumbersOfKeys() == keysBackup.getTotalNumbersOfBackedUpKeys()
}
// -> That must fail and her backup state must be WrongBackUpVersion // -> That must fail and her backup state must be WrongBackUpVersion
assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.getState()) assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.getState())
@ -980,11 +924,17 @@ class KeysBackupTest : InstrumentedTest {
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
// Wait for keys backup to finish by asking again to backup keys. // Wait for keys backup to finish by asking again to backup keys.
testHelper.waitForCallback<Unit> { testHelper.retryPeriodically {
keysBackup.backupAllGroupSessions(null, it) keysBackup.getTotalNumbersOfKeys() == keysBackup.getTotalNumbersOfBackedUpKeys()
} }
testHelper.retryPeriodically {
keysBackup.getState() == KeysBackupState.ReadyToBackUp
}
// testHelper.doSync<Unit> {
// keysBackup.backupAllGroupSessions(null, it)
// }
val oldDeviceId = cryptoTestData.firstSession.sessionParams.deviceId!! val oldDeviceId = cryptoTestData.firstSession.sessionParams.deviceId
val oldKeyBackupVersion = keysBackup.currentBackupVersion val oldKeyBackupVersion = keysBackup.currentBackupVersion
val aliceUserId = cryptoTestData.firstSession.myUserId val aliceUserId = cryptoTestData.firstSession.myUserId
@ -1005,18 +955,16 @@ class KeysBackupTest : InstrumentedTest {
val stateObserver2 = StateObserver(keysBackup2) val stateObserver2 = StateObserver(keysBackup2)
testHelper.waitForCallbackError<Unit> { keysBackup2.backupAllGroupSessions(null, it) } testHelper.retryPeriodically {
keysBackup2.getTotalNumbersOfKeys() == keysBackup2.getTotalNumbersOfBackedUpKeys()
}
// Backup state must be NotTrusted // Backup state must be NotTrusted
assertEquals("Backup state must be NotTrusted", KeysBackupState.NotTrusted, keysBackup2.getState()) assertEquals("Backup state must be NotTrusted", KeysBackupState.NotTrusted, keysBackup2.getState())
assertFalse("Backup should not be enabled", keysBackup2.isEnabled()) assertFalse("Backup should not be enabled", keysBackup2.isEnabled())
// - Validate the old device from the new one // - Validate the old device from the new one
aliceSession2.cryptoService().setDeviceVerification( aliceSession2.cryptoService().verificationService().markedLocallyAsManuallyVerified(aliceSession2.myUserId, oldDeviceId)
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
aliceSession2.myUserId,
oldDeviceId
)
// -> Backup should automatically enable on the new device // -> Backup should automatically enable on the new device
suspendCancellableCoroutine<Unit> { continuation -> suspendCancellableCoroutine<Unit> { continuation ->
@ -1037,8 +985,13 @@ class KeysBackupTest : InstrumentedTest {
// -> It must use the same backup version // -> It must use the same backup version
assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion) assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion)
testHelper.waitForCallback<Unit> { // aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it)
aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it) testHelper.retryPeriodically {
keysBackup2.getTotalNumbersOfKeys() == keysBackup2.getTotalNumbersOfBackedUpKeys()
}
testHelper.retryPeriodically {
aliceSession2.cryptoService().keysBackupService().getState() == KeysBackupState.ReadyToBackUp
} }
// -> It must success // -> It must success
@ -1070,7 +1023,7 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(keysBackup.isEnabled()) assertTrue(keysBackup.isEnabled())
// Delete the backup // Delete the backup
testHelper.waitForCallback<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) } keysBackup.deleteBackup(keyBackupCreationInfo.version)
// Backup is now disabled // Backup is now disabled
assertFalse(keysBackup.isEnabled()) assertFalse(keysBackup.isEnabled())

View File

@ -0,0 +1,613 @@
/*
* 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.verification
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.amshove.kluent.internal.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import org.matrix.android.sdk.api.session.crypto.verification.SasTransactionState
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationEvent
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import timber.log.Timber
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class SASTest : InstrumentedTest {
val scope = CoroutineScope(SupervisorJob())
@Test
fun test_aliceStartThenAliceCancel() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
Timber.v("verification: doE2ETestWithAliceAndBobInARoom")
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
Timber.v("verification: initializeCrossSigning")
cryptoTestData.initializeCrossSigning(cryptoTestHelper)
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService()
Timber.v("verification: requestVerificationAndWaitForReadyState")
val txId = SasVerificationTestHelper(testHelper)
.requestVerificationAndWaitForReadyState(cryptoTestData, listOf(VerificationMethod.SAS))
Timber.v("verification: startKeyVerification")
aliceVerificationService.startKeyVerification(
VerificationMethod.SAS,
bobSession.myUserId,
txId
)
Timber.v("verification: ensure bob has received starete")
testHelper.retryWithBackoff {
bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, txId)?.state == EVerificationState.Started
}
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txId)
assertNotNull("Bob should have started verif transaction", bobKeyTx)
assertTrue(bobKeyTx is SasVerificationTransaction)
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txId)
assertTrue(aliceKeyTx is SasVerificationTransaction)
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
val aliceCancelled = CompletableDeferred<Unit>()
aliceVerificationService.requestEventFlow().onEach {
println("alice flow event $it")
if (it is VerificationEvent.TransactionUpdated && it.transactionId == txId) {
val sasVerificationTransaction = it.transaction as SasVerificationTransaction
if (sasVerificationTransaction.state() is SasTransactionState.Cancelled) {
aliceCancelled.complete(Unit)
}
}
}.launchIn(scope)
val bobCancelled = CompletableDeferred<Unit>()
bobVerificationService.requestEventFlow().onEach {
println("alice flow event $it")
if (it is VerificationEvent.TransactionUpdated && it.transactionId == txId) {
val sasVerificationTransaction = it.transaction as SasVerificationTransaction
if (sasVerificationTransaction.state() is SasTransactionState.Cancelled) {
bobCancelled.complete(Unit)
}
}
}.launchIn(scope)
aliceVerificationService.cancelVerificationRequest(bobSession.myUserId, txId)
aliceCancelled.await()
bobCancelled.await()
val cancelledAlice = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId, txId)!!
val cancelledBob = aliceVerificationService.getExistingVerificationRequest(aliceSession.myUserId, txId)!!
assertEquals("Should be cancelled on alice side", cancelledAlice.state, EVerificationState.Cancelled)
assertEquals("Should be cancelled on alice side", cancelledBob.state, EVerificationState.Cancelled)
assertEquals("Should be User cancelled on alice side", CancelCode.User, cancelledAlice.cancelConclusion)
assertEquals("Should be User cancelled on bob side", CancelCode.User, cancelledBob.cancelConclusion)
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txId))
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txId))
}
/*
@Test
@Ignore("This test will be ignored until it is fixed")
fun test_key_agreement_protocols_must_include_curve25519() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
fail("Not passing for the moment")
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
val protocols = listOf("meh_dont_know")
val tid = "00000000"
// Bob should receive a cancel
var cancelReason: CancelCode? = null
val cancelLatch = CountDownLatch(1)
val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
tx as SasVerificationTransaction
if (tx.transactionId == tid && tx.state() is SasTransactionState.Cancelled) {
cancelReason = (tx.state() as SasTransactionState.Cancelled).cancelCode
cancelLatch.countDown()
}
}
}
// bobSession.cryptoService().verificationService().addListener(bobListener)
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
// TODO override fun onToDeviceEvent(event: Event?) {
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
// TODO canceledToDeviceEvent = event
// TODO cancelLatch.countDown()
// TODO }
// TODO }
// TODO }
// TODO })
val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.cryptoService().getMyCryptoDevice().deviceId
val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
tx as SasVerificationTransaction
if (tx.state() is SasTransactionState.SasStarted) {
runBlocking {
tx.acceptVerification()
}
}
}
}
// aliceSession.cryptoService().verificationService().addListener(aliceListener)
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
testHelper.await(cancelLatch)
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
}
@Test
@Ignore("This test will be ignored until it is fixed")
fun test_key_agreement_macs_Must_include_hmac_sha256() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
fail("Not passing for the moment")
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
val mac = listOf("shaBit")
val tid = "00000000"
// Bob should receive a cancel
val canceledToDeviceEvent: Event? = null
val cancelLatch = CountDownLatch(1)
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
// TODO override fun onToDeviceEvent(event: Event?) {
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
// TODO canceledToDeviceEvent = event
// TODO cancelLatch.countDown()
// TODO }
// TODO }
// TODO }
// TODO })
val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.cryptoService().getMyCryptoDevice().deviceId
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
testHelper.await(cancelLatch)
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
}
@Test
@Ignore("This test will be ignored until it is fixed")
fun test_key_agreement_short_code_include_decimal() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
fail("Not passing for the moment")
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
val codes = listOf("bin", "foo", "bar")
val tid = "00000000"
// Bob should receive a cancel
var canceledToDeviceEvent: Event? = null
val cancelLatch = CountDownLatch(1)
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
// TODO override fun onToDeviceEvent(event: Event?) {
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
// TODO canceledToDeviceEvent = event
// TODO cancelLatch.countDown()
// TODO }
// TODO }
// TODO }
// TODO })
val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.cryptoService().getMyCryptoDevice().deviceId
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
testHelper.await(cancelLatch)
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
}
private suspend fun fakeBobStart(
bobSession: Session,
aliceUserID: String?,
aliceDevice: String?,
tid: String,
protocols: List<String> = SasVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
hashes: List<String> = SasVerificationTransaction.KNOWN_HASHES,
mac: List<String> = SasVerificationTransaction.KNOWN_MACS,
codes: List<String> = SasVerificationTransaction.KNOWN_SHORT_CODES
) {
val startMessage = KeyVerificationStart(
fromDevice = bobSession.cryptoService().getMyCryptoDevice().deviceId,
method = VerificationMethod.SAS.toValue(),
transactionId = tid,
keyAgreementProtocols = protocols,
hashes = hashes,
messageAuthenticationCodes = mac,
shortAuthenticationStrings = codes
)
val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(aliceUserID, aliceDevice, startMessage)
// TODO val sendLatch = CountDownLatch(1)
// TODO bobSession.cryptoRestClient.sendToDevice(
// TODO EventType.KEY_VERIFICATION_START,
// TODO contentMap,
// TODO tid,
// TODO TestMatrixCallback<Void>(sendLatch)
// TODO )
}
// any two devices may only have at most one key verification in flight at a time.
// If a device has two verifications in progress with the same device, then it should cancel both verifications.
@Test
fun test_aliceStartTwoRequests() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val aliceCreatedLatch = CountDownLatch(2)
val aliceCancelledLatch = CountDownLatch(1)
val createdTx = mutableListOf<VerificationTransaction>()
val aliceListener = object : VerificationService.Listener {
override fun transactionCreated(tx: VerificationTransaction) {
createdTx.add(tx)
aliceCreatedLatch.countDown()
}
override fun transactionUpdated(tx: VerificationTransaction) {
tx as SasVerificationTransaction
if (tx.state() is SasTransactionState.Cancelled && !(tx.state() as SasTransactionState.Cancelled).byMe) {
aliceCancelledLatch.countDown()
}
}
}
// aliceVerificationService.addListener(aliceListener)
val bobUserId = bobSession!!.myUserId
val bobDeviceId = bobSession.cryptoService().getMyCryptoDevice().deviceId
// TODO
// aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), forceDownload = true)
// aliceVerificationService.beginKeyVerification(listOf(VerificationMethod.SAS), bobUserId, bobDeviceId)
// aliceVerificationService.beginKeyVerification(bobUserId, bobDeviceId)
// testHelper.await(aliceCreatedLatch)
// testHelper.await(aliceCancelledLatch)
cryptoTestData.cleanUp(testHelper)
}
/**
* Test that when alice starts a 'correct' request, bob agrees.
*/
// @Test
// @Ignore("This test will be ignored until it is fixed")
// fun test_aliceAndBobAgreement() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
// val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
//
// val aliceSession = cryptoTestData.firstSession
// val bobSession = cryptoTestData.secondSession
//
// val aliceVerificationService = aliceSession.cryptoService().verificationService()
// val bobVerificationService = bobSession!!.cryptoService().verificationService()
//
// val aliceAcceptedLatch = CountDownLatch(1)
// val aliceListener = object : VerificationService.Listener {
// override fun transactionUpdated(tx: VerificationTransaction) {
// if (tx.state() is VerificationTxState.OnAccepted) {
// aliceAcceptedLatch.countDown()
// }
// }
// }
// aliceVerificationService.addListener(aliceListener)
//
// val bobListener = object : VerificationService.Listener {
// override fun transactionUpdated(tx: VerificationTransaction) {
// if (tx.state() is VerificationTxState.OnStarted && tx is SasVerificationTransaction) {
// bobVerificationService.removeListener(this)
// runBlocking {
// tx.acceptVerification()
// }
// }
// }
// }
// bobVerificationService.addListener(bobListener)
//
// val bobUserId = bobSession.myUserId
// val bobDeviceId = runBlocking {
// bobSession.cryptoService().getMyCryptoDevice().deviceId
// }
//
// aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
// testHelper.await(aliceAcceptedLatch)
//
// aliceVerificationService.getExistingTransaction(bobUserId, )
//
// assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
//
// // check that agreement is valid
// assertTrue("Agreed Protocol should be Valid", accepted != null)
// assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols.contains(accepted!!.keyAgreementProtocol))
// assertTrue("Hash should be known by alice", startReq!!.hashes.contains(accepted!!.hash))
// assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes.contains(accepted!!.messageAuthenticationCode))
//
// accepted!!.shortAuthenticationStrings.forEach {
// assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
// }
// }
// @Test
// fun test_aliceAndBobSASCode() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
// val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
// cryptoTestData.initializeCrossSigning(cryptoTestHelper)
// val sasTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
// val aliceSession = cryptoTestData.firstSession
// val bobSession = cryptoTestData.secondSession!!
// val transactionId = sasTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, supportedMethods)
//
// val latch = CountDownLatch(2)
// val aliceListener = object : VerificationService.Listener {
// override fun transactionUpdated(tx: VerificationTransaction) {
// Timber.v("Alice transactionUpdated: ${tx.state()}")
// latch.countDown()
// }
// }
// aliceSession.cryptoService().verificationService().addListener(aliceListener)
// val bobListener = object : VerificationService.Listener {
// override fun transactionUpdated(tx: VerificationTransaction) {
// Timber.v("Bob transactionUpdated: ${tx.state()}")
// latch.countDown()
// }
// }
// bobSession.cryptoService().verificationService().addListener(bobListener)
// aliceSession.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, transactionId)
//
// testHelper.await(latch)
// val aliceTx =
// aliceSession.cryptoService().verificationService().getExistingTransaction(bobSession.myUserId, transactionId) as SasVerificationTransaction
// val bobTx = bobSession.cryptoService().verificationService().getExistingTransaction(aliceSession.myUserId, transactionId) as SasVerificationTransaction
//
// assertEquals("Should have same SAS", aliceTx.getDecimalCodeRepresentation(), bobTx.getDecimalCodeRepresentation())
//
// val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
// val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
//
// assertEquals(
// "Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
// bobTx.getShortCodeRepresentation(SasMode.DECIMAL)
// )
// }
@Test
fun test_happyPath() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
cryptoTestData.initializeCrossSigning(cryptoTestHelper)
val sasVerificationTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
val transactionId = sasVerificationTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, listOf(VerificationMethod.SAS))
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService()
val verifiedLatch = CountDownLatch(2)
val aliceListener = object : VerificationService.Listener {
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
Timber.v("RequestUpdated pr=$pr")
}
var matched = false
var verified = false
override fun transactionUpdated(tx: VerificationTransaction) {
if (tx !is SasVerificationTransaction) return
Timber.v("Alice transactionUpdated: ${tx.state()} on thread:${Thread.currentThread()}")
when (tx.state()) {
SasTransactionState.SasShortCodeReady -> {
if (!matched) {
matched = true
runBlocking {
delay(500)
tx.userHasVerifiedShortCode()
}
}
}
is SasTransactionState.Done -> {
if (!verified) {
verified = true
verifiedLatch.countDown()
}
}
else -> Unit
}
}
}
// aliceVerificationService.addListener(aliceListener)
val bobListener = object : VerificationService.Listener {
var accepted = false
var matched = false
var verified = false
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
Timber.v("RequestUpdated: pr=$pr")
}
override fun transactionUpdated(tx: VerificationTransaction) {
if (tx !is SasVerificationTransaction) return
Timber.v("Bob transactionUpdated: ${tx.state()} on thread: ${Thread.currentThread()}")
when (tx.state()) {
// VerificationTxState.SasStarted -> {
// if (!accepted) {
// accepted = true
// runBlocking {
// tx.acceptVerification()
// }
// }
// }
SasTransactionState.SasShortCodeReady -> {
if (!matched) {
matched = true
runBlocking {
delay(500)
tx.userHasVerifiedShortCode()
}
}
}
is SasTransactionState.Done -> {
if (!verified) {
verified = true
verifiedLatch.countDown()
}
}
else -> Unit
}
}
}
// bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId
val bobDeviceId = runBlocking {
bobSession.cryptoService().getMyCryptoDevice().deviceId
}
aliceVerificationService.startKeyVerification(VerificationMethod.SAS, bobUserId, transactionId)
Timber.v("Await after beginKey ${Thread.currentThread()}")
testHelper.await(verifiedLatch)
// Assert that devices are verified
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId)
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? =
bobSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyCryptoDevice().deviceId)
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
}
@Test
fun test_ConcurrentStart() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
cryptoTestData.initializeCrossSigning(cryptoTestHelper)
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession.cryptoService().verificationService()
val req = aliceVerificationService.requestKeyVerificationInDMs(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
bobSession.myUserId,
cryptoTestData.roomId
)
val requestID = req.transactionId
Log.v("TEST", "== requestID is $requestID")
testHelper.retryPeriodically {
val prBobPOV = bobVerificationService.getExistingVerificationRequests(aliceSession.myUserId).firstOrNull()
Log.v("TEST", "== prBobPOV is $prBobPOV")
prBobPOV?.transactionId == requestID
}
bobVerificationService.readyPendingVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
aliceSession.myUserId,
requestID
)
// wait for alice to get the ready
testHelper.retryPeriodically {
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
Log.v("TEST", "== prAlicePOV is $prAlicePOV")
prAlicePOV?.transactionId == requestID && prAlicePOV.state == EVerificationState.Ready
}
// Start concurrent!
aliceVerificationService.startKeyVerification(
method = VerificationMethod.SAS,
otherUserId = bobSession.myUserId,
requestId = requestID,
)
bobVerificationService.startKeyVerification(
method = VerificationMethod.SAS,
otherUserId = aliceSession.myUserId,
requestId = requestID,
)
// we should reach SHOW SAS on both
var alicePovTx: SasVerificationTransaction?
var bobPovTx: SasVerificationTransaction?
testHelper.retryPeriodically {
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID) as? SasVerificationTransaction
Log.v("TEST", "== alicePovTx is $alicePovTx")
alicePovTx?.state() == SasTransactionState.SasShortCodeReady
}
// wait for alice to get the ready
testHelper.retryPeriodically {
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID) as? SasVerificationTransaction
Log.v("TEST", "== bobPovTx is $bobPovTx")
bobPovTx?.state() == SasTransactionState.SasShortCodeReady
}
}
*/
}

View File

@ -0,0 +1,127 @@
/*
* 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.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.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.matrix.android.sdk.InstrumentedTest
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.model.CryptoMetadataEntity
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.database.TestRealmConfigurationFactory
import org.matrix.android.sdk.internal.session.MigrateEAtoEROperation
import org.matrix.android.sdk.internal.util.time.Clock
import org.matrix.olm.OlmAccount
import org.matrix.olm.OlmManager
import org.matrix.rustcomponents.sdk.crypto.OlmMachine
import java.io.File
@RunWith(AndroidJUnit4::class)
class ElementAndroidToElementRMigrationTest : InstrumentedTest {
@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()
}
@Test
fun given_a_valid_crypto_store_realm_file_then_migration_should_be_successful() {
val realmName = "crypto_store_migration_16.realm"
val migration = RealmCryptoStoreMigration(object : Clock {
override fun epochMillis() = 0L
})
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 extractor = MigrateEAtoEROperation()
val targetFile = File(configurationFactory.root, "rust-sdk")
extractor.execute(realmConfiguration, targetFile)
val machine = OlmMachine(userId, deviceId, targetFile.path, null)
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)
val inboundGroupSessionEntities = realm!!.where<OlmInboundGroupSessionEntity>().findAll()
assertEquals(inboundGroupSessionEntities.size, machine.roomKeyCounts().total.toInt())
val backedUpInboundGroupSessionEntities = realm!!
.where<OlmInboundGroupSessionEntity>()
.equalTo(OlmInboundGroupSessionEntityFields.BACKED_UP, true)
.findAll()
assertEquals(backedUpInboundGroupSessionEntities.size, machine.roomKeyCounts().backedUp.toInt())
}
// @Test
// fun given_an_empty_crypto_store_realm_file_then_migration_should_not_happen() {
// val realmConfiguration = realmConfigurationFactory.configurationForMigrationFrom15To16(populateCryptoStore = false)
// Realm.getInstance(realmConfiguration).use {
// assertTrue(it.isEmpty)
// }
// val machine = OlmMachine("@ganfra146:matrix.org", "UTDQCHKKNS", realmConfigurationFactory.root.path, null)
// assertNull(machine.getBackupKeys())
// val crossSigningStatus = machine.crossSigningStatus()
// assertFalse(crossSigningStatus.hasMaster)
// assertFalse(crossSigningStatus.hasSelfSigning)
// assertFalse(crossSigningStatus.hasUserSigning)
// }
}

View File

@ -0,0 +1,57 @@
/*
* 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.session.crypto.keysbackup
import org.matrix.android.sdk.api.util.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.tools.withOlmDecryption
import org.matrix.olm.OlmPkMessage
class BackupRecoveryKey(private val key: ByteArray) : IBackupRecoveryKey {
override fun equals(other: Any?): Boolean {
if (other !is BackupRecoveryKey) return false
return this.toBase58() == other.toBase58()
}
override fun toBase58() = computeRecoveryKey(key)
override fun toBase64() = key.toBase64NoPadding()
override fun decryptV1(ephemeralKey: String, mac: String, ciphertext: String): String = withOlmDecryption {
it.setPrivateKey(key)
it.decrypt(OlmPkMessage().apply {
this.mEphemeralKey = ephemeralKey
this.mCipherText = ciphertext
this.mMac = mac
})
}
override fun megolmV1PublicKey() = v1pk
private val v1pk = object : IMegolmV1PublicKey {
override val publicKey: String
get() = withOlmDecryption {
it.setPrivateKey(key)
}
override val privateKeySalt: String?
get() = null // not use in kotlin sdk
override val privateKeyIterations: Int?
get() = null // not use in kotlin sdk
override val backupAlgorithm: String
get() = "" // not use in kotlin sdk
}
}

View File

@ -0,0 +1,32 @@
/*
* 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.session.crypto.keysbackup
object BackupUtils {
fun recoveryKeyFromBase58(base58: String): IBackupRecoveryKey? {
return extractCurveKeyFromRecoveryKey(base58)?.let {
BackupRecoveryKey(it)
}
}
fun recoveryKeyFromPassphrase(passphrase: String): IBackupRecoveryKey? {
return extractCurveKeyFromRecoveryKey(passphrase)?.let {
BackupRecoveryKey(it)
}
}
}

View File

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.sync.handler package org.matrix.android.sdk.internal.session.sync.handler
import dagger.Lazy
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM
import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.MXCryptoError
@ -26,8 +27,6 @@ import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
import org.matrix.android.sdk.internal.crypto.tasks.toDeviceTracingId import org.matrix.android.sdk.internal.crypto.tasks.toDeviceTracingId
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
@ -38,15 +37,14 @@ import javax.inject.Inject
private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO) private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
internal class CryptoSyncHandler @Inject constructor( internal class CryptoSyncHandler @Inject constructor(
private val cryptoService: DefaultCryptoService, private val cryptoService: Lazy<DefaultCryptoService>,
private val verificationService: DefaultVerificationService private val verificationService: DefaultVerificationService
) { ) {
suspend fun handleToDevice(toDevice: ToDeviceSyncResponse, progressReporter: ProgressReporter? = null) { suspend fun handleToDevice(eventList: List<Event>, progressReporter: ProgressReporter? = null) {
val total = toDevice.events?.size ?: 0 val total = eventList.size
toDevice.events eventList.filter { isSupportedToDevice(it) }
?.filter { isSupportedToDevice(it) } .forEachIndexed { index, event ->
?.forEachIndexed { index, event ->
progressReporter?.reportProgress(index * 100F / total) progressReporter?.reportProgress(index * 100F / total)
// Decrypt event if necessary // Decrypt event if necessary
Timber.tag(loggerTag.value).d("To device event msgid:${event.toDeviceTracingId()}") Timber.tag(loggerTag.value).d("To device event msgid:${event.toDeviceTracingId()}")
@ -58,7 +56,7 @@ internal class CryptoSyncHandler @Inject constructor(
} else { } else {
Timber.tag(loggerTag.value).d("received to-device ${event.getClearType()} from:${event.senderId} msgid:${event.toDeviceTracingId()}") Timber.tag(loggerTag.value).d("received to-device ${event.getClearType()} from:${event.senderId} msgid:${event.toDeviceTracingId()}")
verificationService.onToDeviceEvent(event) verificationService.onToDeviceEvent(event)
cryptoService.onToDeviceEvent(event) cryptoService.get().onToDeviceEvent(event)
} }
} }
} }
@ -85,10 +83,6 @@ internal class CryptoSyncHandler @Inject constructor(
} }
} }
fun onSyncCompleted(syncResponse: SyncResponse) {
cryptoService.onSyncCompleted(syncResponse)
}
/** /**
* Decrypt an encrypted event. * Decrypt an encrypted event.
* *
@ -101,12 +95,12 @@ internal class CryptoSyncHandler @Inject constructor(
if (event.getClearType() == EventType.ENCRYPTED) { if (event.getClearType() == EventType.ENCRYPTED) {
var result: MXEventDecryptionResult? = null var result: MXEventDecryptionResult? = null
try { try {
result = cryptoService.decryptEvent(event, timelineId ?: "") result = cryptoService.get().decryptEvent(event, timelineId ?: "")
} catch (exception: MXCryptoError) { } catch (exception: MXCryptoError) {
event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType // setCryptoError(exception.cryptoError) event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType // setCryptoError(exception.cryptoError)
val senderKey = event.content.toModel<OlmEventContent>()?.senderKey ?: "<unknown sender key>" val senderKey = event.content.toModel<OlmEventContent>()?.senderKey ?: "<unknown sender key>"
// try to find device id to ease log reading // try to find device id to ease log reading
val deviceId = cryptoService.getCryptoDeviceInfo(event.senderId!!).firstOrNull { val deviceId = cryptoService.get().getCryptoDeviceInfo(event.senderId!!).firstOrNull {
it.identityKey() == senderKey it.identityKey() == senderKey
}?.deviceId ?: senderKey }?.deviceId ?: senderKey
Timber.e("## CRYPTO | Failed to decrypt to device event from ${event.senderId}|$deviceId reason:<${event.mCryptoError ?: exception}>") Timber.e("## CRYPTO | Failed to decrypt to device event from ${event.senderId}|$deviceId reason:<${event.mCryptoError ?: exception}>")

View File

@ -0,0 +1,262 @@
/*
* Copyright (c) 2019 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
import dagger.Binds
import dagger.Module
import dagger.Provides
import io.realm.RealmConfiguration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultCreateKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultDeleteBackupTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultDeleteSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupLastVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultGetSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultStoreSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultUpdateKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteBackupTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore
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.tasks.ClaimOneTimeKeysForUsersDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers
import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultGetDeviceInfoTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultGetDevicesTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultInitializeCrossSigningTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendEventTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendToDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSetDeviceNameTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSignaturesTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSigningKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
import org.matrix.android.sdk.internal.crypto.tasks.EncryptEventTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask
import org.matrix.android.sdk.internal.crypto.tasks.InitializeCrossSigningTask
import org.matrix.android.sdk.internal.crypto.tasks.SendEventTask
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
import org.matrix.android.sdk.internal.database.RealmKeysUtils
import org.matrix.android.sdk.internal.di.CryptoDatabase
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
import org.matrix.android.sdk.internal.di.UserMd5
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.cache.ClearCacheTask
import org.matrix.android.sdk.internal.session.cache.RealmClearCacheTask
import retrofit2.Retrofit
import java.io.File
@Module
internal abstract class CryptoModule {
@Module
companion object {
internal fun getKeyAlias(userMd5: String) = "crypto_module_$userMd5"
@JvmStatic
@Provides
@CryptoDatabase
@SessionScope
fun providesRealmConfiguration(
@SessionFilesDirectory directory: File,
@UserMd5 userMd5: String,
realmKeysUtils: RealmKeysUtils,
realmCryptoStoreMigration: RealmCryptoStoreMigration
): RealmConfiguration {
return RealmConfiguration.Builder()
.directory(directory)
.apply {
realmKeysUtils.configureEncryption(this, getKeyAlias(userMd5))
}
.name("crypto_store.realm")
.modules(RealmCryptoStoreModule())
.allowWritesOnUiThread(true)
.schemaVersion(realmCryptoStoreMigration.schemaVersion)
.migration(realmCryptoStoreMigration)
.build()
}
@JvmStatic
@Provides
@SessionScope
fun providesCryptoCoroutineScope(coroutineDispatchers: MatrixCoroutineDispatchers): CoroutineScope {
return CoroutineScope(SupervisorJob() + coroutineDispatchers.crypto)
}
@JvmStatic
@Provides
@CryptoDatabase
fun providesClearCacheTask(@CryptoDatabase realmConfiguration: RealmConfiguration): ClearCacheTask {
return RealmClearCacheTask(realmConfiguration)
}
@JvmStatic
@Provides
@SessionScope
fun providesCryptoAPI(retrofit: Retrofit): CryptoApi {
return retrofit.create(CryptoApi::class.java)
}
@JvmStatic
@Provides
@SessionScope
fun providesRoomKeysAPI(retrofit: Retrofit): RoomKeysApi {
return retrofit.create(RoomKeysApi::class.java)
}
}
@Binds
abstract fun bindCryptoService(service: DefaultCryptoService): CryptoService
@Binds
abstract fun bindKeysBackupService(service: DefaultKeysBackupService): KeysBackupService
@Binds
abstract fun bindDeleteDeviceTask(task: DefaultDeleteDeviceTask): DeleteDeviceTask
@Binds
abstract fun bindGetDevicesTask(task: DefaultGetDevicesTask): GetDevicesTask
@Binds
abstract fun bindGetDeviceInfoTask(task: DefaultGetDeviceInfoTask): GetDeviceInfoTask
@Binds
abstract fun bindSetDeviceNameTask(task: DefaultSetDeviceNameTask): SetDeviceNameTask
@Binds
abstract fun bindUploadKeysTask(task: DefaultUploadKeysTask): UploadKeysTask
@Binds
abstract fun bindUploadSigningKeysTask(task: DefaultUploadSigningKeysTask): UploadSigningKeysTask
@Binds
abstract fun bindUploadSignaturesTask(task: DefaultUploadSignaturesTask): UploadSignaturesTask
@Binds
abstract fun bindDownloadKeysForUsersTask(task: DefaultDownloadKeysForUsers): DownloadKeysForUsersTask
@Binds
abstract fun bindCreateKeysBackupVersionTask(task: DefaultCreateKeysBackupVersionTask): CreateKeysBackupVersionTask
@Binds
abstract fun bindDeleteBackupTask(task: DefaultDeleteBackupTask): DeleteBackupTask
@Binds
abstract fun bindDeleteRoomSessionDataTask(task: DefaultDeleteRoomSessionDataTask): DeleteRoomSessionDataTask
@Binds
abstract fun bindDeleteRoomSessionsDataTask(task: DefaultDeleteRoomSessionsDataTask): DeleteRoomSessionsDataTask
@Binds
abstract fun bindDeleteSessionsDataTask(task: DefaultDeleteSessionsDataTask): DeleteSessionsDataTask
@Binds
abstract fun bindGetKeysBackupLastVersionTask(task: DefaultGetKeysBackupLastVersionTask): GetKeysBackupLastVersionTask
@Binds
abstract fun bindGetKeysBackupVersionTask(task: DefaultGetKeysBackupVersionTask): GetKeysBackupVersionTask
@Binds
abstract fun bindGetRoomSessionDataTask(task: DefaultGetRoomSessionDataTask): GetRoomSessionDataTask
@Binds
abstract fun bindGetRoomSessionsDataTask(task: DefaultGetRoomSessionsDataTask): GetRoomSessionsDataTask
@Binds
abstract fun bindGetSessionsDataTask(task: DefaultGetSessionsDataTask): GetSessionsDataTask
@Binds
abstract fun bindStoreRoomSessionDataTask(task: DefaultStoreRoomSessionDataTask): StoreRoomSessionDataTask
@Binds
abstract fun bindStoreRoomSessionsDataTask(task: DefaultStoreRoomSessionsDataTask): StoreRoomSessionsDataTask
@Binds
abstract fun bindStoreSessionsDataTask(task: DefaultStoreSessionsDataTask): StoreSessionsDataTask
@Binds
abstract fun bindUpdateKeysBackupVersionTask(task: DefaultUpdateKeysBackupVersionTask): UpdateKeysBackupVersionTask
@Binds
abstract fun bindSendToDeviceTask(task: DefaultSendToDeviceTask): SendToDeviceTask
@Binds
abstract fun bindEncryptEventTask(task: DefaultEncryptEventTask): EncryptEventTask
@Binds
abstract fun bindSendVerificationMessageTask(task: DefaultSendVerificationMessageTask): SendVerificationMessageTask
@Binds
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(task: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask
@Binds
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService
@Binds
abstract fun bindVerificationService(service: DefaultVerificationService): VerificationService
@Binds
abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore
@Binds
abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask
@Binds
abstract fun bindInitalizeCrossSigningTask(task: DefaultInitializeCrossSigningTask): InitializeCrossSigningTask
}

View File

@ -0,0 +1,146 @@
/*
* 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
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import javax.inject.Inject
internal class DecryptRoomEventUseCase @Inject constructor(
private val olmDevice: MXOlmDevice,
private val cryptoStore: IMXCryptoStore,
private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
) {
suspend operator fun invoke(event: Event, requestKeysOnFail: Boolean = true): MXEventDecryptionResult {
if (event.roomId.isNullOrBlank()) {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
}
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
if (encryptedEventContent.senderKey.isNullOrBlank() ||
encryptedEventContent.sessionId.isNullOrBlank() ||
encryptedEventContent.ciphertext.isNullOrBlank()) {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
}
try {
val olmDecryptionResult = olmDevice.decryptGroupMessage(
encryptedEventContent.ciphertext,
event.roomId,
"",
eventId = event.eventId.orEmpty(),
encryptedEventContent.sessionId,
encryptedEventContent.senderKey
)
if (olmDecryptionResult.payload != null) {
return MXEventDecryptionResult(
clearEvent = olmDecryptionResult.payload,
senderCurve25519Key = olmDecryptionResult.senderKey,
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain
.orEmpty(),
isSafe = olmDecryptionResult.isSafe.orFalse()
)
} else {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
}
} catch (throwable: Throwable) {
if (throwable is MXCryptoError.OlmError) {
// TODO Check the value of .message
if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
// So we know that session, but it's ratcheted and we can't decrypt at that index
// Check if partially withheld
val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)
if (withHeldInfo != null) {
// Encapsulate as withHeld exception
throw MXCryptoError.Base(
MXCryptoError.ErrorType.KEYS_WITHHELD,
withHeldInfo.code?.value ?: "",
withHeldInfo.reason
)
}
throw MXCryptoError.Base(
MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX,
"UNKNOWN_MESSAGE_INDEX",
null
)
}
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason)
throw MXCryptoError.Base(
MXCryptoError.ErrorType.OLM,
reason,
detailedReason
)
}
if (throwable is MXCryptoError.Base) {
if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
// Check if it was withheld by sender to enrich error code
val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)
if (withHeldInfo != null) {
if (requestKeysOnFail) {
requestKeysForEvent(event)
}
// Encapsulate as withHeld exception
throw MXCryptoError.Base(
MXCryptoError.ErrorType.KEYS_WITHHELD,
withHeldInfo.code?.value ?: "",
withHeldInfo.reason
)
}
if (requestKeysOnFail) {
requestKeysForEvent(event)
}
}
}
throw throwable
}
}
private fun requestKeysForEvent(event: Event) {
outgoingKeyRequestManager.requestKeyForEvent(event, false)
}
suspend fun decryptAndSaveResult(event: Event) {
tryOrNull(message = "Unable to decrypt the event") {
invoke(event)
}
?.let { result ->
event.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent,
senderKey = result.senderCurve25519Key,
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain,
isSafe = result.isSafe
)
}
}
}

View File

@ -53,7 +53,6 @@ import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListen
import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
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.DeviceInfo import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo
@ -73,7 +72,10 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.shouldShareHistory import org.matrix.android.sdk.api.session.room.model.shouldShareHistory
import org.matrix.android.sdk.api.session.sync.model.DeviceListResponse
import org.matrix.android.sdk.api.session.sync.model.DeviceOneTimeKeysCountSyncResponse
import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.api.session.sync.model.SyncResponse
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
@ -86,6 +88,7 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningSe
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE
import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.crypto.model.SessionInfo
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadBody
import org.matrix.android.sdk.internal.crypto.model.toRest import org.matrix.android.sdk.internal.crypto.model.toRest
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@ -103,9 +106,7 @@ import org.matrix.android.sdk.internal.extensions.foldToCallback
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.StreamEventsManager import org.matrix.android.sdk.internal.session.StreamEventsManager
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.session.sync.handler.CryptoSyncHandler
import org.matrix.android.sdk.internal.task.TaskThread
import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.task.launchToCallback
import org.matrix.android.sdk.internal.util.JsonCanonicalizer import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.android.sdk.internal.util.time.Clock
@ -181,18 +182,18 @@ internal class DefaultCryptoService @Inject constructor(
private val loadRoomMembersTask: LoadRoomMembersTask, private val loadRoomMembersTask: LoadRoomMembersTask,
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor,
private val cryptoCoroutineScope: CoroutineScope, private val cryptoCoroutineScope: CoroutineScope,
private val eventDecryptor: EventDecryptor, private val eventDecryptor: EventDecryptor,
private val verificationMessageProcessor: VerificationMessageProcessor, private val verificationMessageProcessor: VerificationMessageProcessor,
private val liveEventManager: Lazy<StreamEventsManager>, private val liveEventManager: Lazy<StreamEventsManager>,
private val unrequestedForwardManager: UnRequestedForwardManager, private val unrequestedForwardManager: UnRequestedForwardManager,
private val cryptoSyncHandler: CryptoSyncHandler,
) : CryptoService { ) : CryptoService {
private val isStarting = AtomicBoolean(false) private val isStarting = AtomicBoolean(false)
private val isStarted = AtomicBoolean(false) private val isStarted = AtomicBoolean(false)
fun onStateEvent(roomId: String, event: Event) { override suspend fun onStateEvent(roomId: String, event: Event) {
when (event.type) { when (event.type) {
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
@ -200,7 +201,7 @@ internal class DefaultCryptoService @Inject constructor(
} }
} }
fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean) { override suspend fun onLiveEvent(roomId: String, event: Event, initialSync: Boolean) {
// handle state events // handle state events
if (event.isStateEvent()) { if (event.isStateEvent()) {
when (event.type) { when (event.type) {
@ -211,10 +212,10 @@ internal class DefaultCryptoService @Inject constructor(
} }
// handle verification // handle verification
if (!isInitialSync) { if (!initialSync) {
if (event.type != null && verificationMessageProcessor.shouldProcess(event.type)) { if (event.type != null && verificationMessageProcessor.shouldProcess(event.type)) {
cryptoCoroutineScope.launch(coroutineDispatchers.dmVerif) { withContext(coroutineDispatchers.dmVerif) {
verificationMessageProcessor.process(event) verificationMessageProcessor.process(roomId, event)
} }
} }
} }
@ -222,69 +223,48 @@ internal class DefaultCryptoService @Inject constructor(
// val gossipingBuffer = mutableListOf<Event>() // val gossipingBuffer = mutableListOf<Event>()
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) { override suspend fun setDeviceName(deviceId: String, deviceName: String) {
setDeviceNameTask setDeviceNameTask
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) { .execute(SetDeviceNameTask.Params(deviceId, deviceName))
this.executionThread = TaskThread.CRYPTO cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
this.callback = object : MatrixCallback<Unit> { downloadKeys(listOf(userId), true)
override fun onSuccess(data: Unit) { }
// bg refresh of crypto device
downloadKeys(listOf(userId), true, NoOpMatrixCallback())
callback.onSuccess(data)
} }
override fun onFailure(failure: Throwable) { override suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
callback.onFailure(failure) deleteDevices(listOf(deviceId), userInteractiveAuthInterceptor)
}
}
}
.executeBy(taskExecutor)
} }
override fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>) { override suspend fun deleteDevices(deviceIds: List<String>, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
deleteDevices(listOf(deviceId), userInteractiveAuthInterceptor, callback) withContext(coroutineDispatchers.crypto) {
}
override fun deleteDevices(deviceIds: List<String>, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>) {
deleteDeviceTask deleteDeviceTask
.configureWith(DeleteDeviceTask.Params(deviceIds, userInteractiveAuthInterceptor, null)) { .execute(DeleteDeviceTask.Params(deviceIds, userInteractiveAuthInterceptor, null))
this.executionThread = TaskThread.CRYPTO
this.callback = callback
} }
.executeBy(taskExecutor)
} }
override fun getCryptoVersion(context: Context, longFormat: Boolean): String { override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version
} }
override fun getMyDevice(): CryptoDeviceInfo { override fun getMyCryptoDevice(): CryptoDeviceInfo {
return myDeviceInfoHolder.get().myDevice return myDeviceInfoHolder.get().myDevice
} }
override fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>) { override suspend fun fetchDevicesList(): List<DeviceInfo> {
getDevicesTask val data = getDevicesTask
.configureWith { .execute(Unit)
// this.executionThread = TaskThread.CRYPTO
this.callback = object : MatrixCallback<DevicesListResponse> {
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
override fun onSuccess(data: DevicesListResponse) {
// Save in local DB
cryptoStore.saveMyDevicesInfo(data.devices.orEmpty()) cryptoStore.saveMyDevicesInfo(data.devices.orEmpty())
callback.onSuccess(data) return data.devices.orEmpty()
}
}
}
.executeBy(taskExecutor)
} }
override fun getMyDevicesInfoLive(): LiveData<List<DeviceInfo>> { override fun getMyDevicesInfoLive(): LiveData<List<DeviceInfo>> {
return cryptoStore.getLiveMyDevicesInfo() return cryptoStore.getLiveMyDevicesInfo()
} }
override suspend fun fetchDeviceInfo(deviceId: String): DeviceInfo {
return getDeviceInfoTask.execute(GetDeviceInfoTask.Params(deviceId))
}
override fun getMyDevicesInfoLive(deviceId: String): LiveData<Optional<DeviceInfo>> { override fun getMyDevicesInfoLive(deviceId: String): LiveData<Optional<DeviceInfo>> {
return cryptoStore.getLiveMyDevicesInfo(deviceId) return cryptoStore.getLiveMyDevicesInfo(deviceId)
} }
@ -293,8 +273,10 @@ internal class DefaultCryptoService @Inject constructor(
return cryptoStore.getMyDevicesInfo() return cryptoStore.getMyDevicesInfo()
} }
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int { override suspend fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
return cryptoStore.inboundGroupSessionsCount(onlyBackedUp) return withContext(coroutineDispatchers.io) {
cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
}
} }
/** /**
@ -312,7 +294,7 @@ internal class DefaultCryptoService @Inject constructor(
* *
* @return true if the crypto is started * @return true if the crypto is started
*/ */
fun isStarted(): Boolean { override fun isStarted(): Boolean {
return isStarted.get() return isStarted.get()
} }
@ -332,14 +314,12 @@ internal class DefaultCryptoService @Inject constructor(
* devices. * devices.
* *
*/ */
fun start() { override fun start() {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
internalStart() internalStart()
tryOrNull("Failed to update device list on start") {
fetchDevicesList()
} }
// Just update
fetchDevicesList(NoOpMatrixCallback())
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
cryptoStore.tidyUpDataBase() cryptoStore.tidyUpDataBase()
} }
} }
@ -367,8 +347,8 @@ internal class DefaultCryptoService @Inject constructor(
} }
} }
fun onSyncWillProcess(isInitialSync: Boolean) { override suspend fun onSyncWillProcess(isInitialSync: Boolean) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { withContext(coroutineDispatchers.crypto) {
if (isInitialSync) { if (isInitialSync) {
try { try {
// On initial sync, we start all our tracking from // On initial sync, we start all our tracking from
@ -391,6 +371,7 @@ internal class DefaultCryptoService @Inject constructor(
return return
} }
isStarting.set(true) isStarting.set(true)
ensureDevice()
// Open the store // Open the store
cryptoStore.open() cryptoStore.open()
@ -402,7 +383,7 @@ internal class DefaultCryptoService @Inject constructor(
/** /**
* Close the crypto. * Close the crypto.
*/ */
fun close() = runBlocking(coroutineDispatchers.crypto) { override fun close() = runBlocking(coroutineDispatchers.crypto) {
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module")) cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
incomingKeyRequestManager.close() incomingKeyRequestManager.close()
outgoingKeyRequestManager.close() outgoingKeyRequestManager.close()
@ -431,16 +412,14 @@ internal class DefaultCryptoService @Inject constructor(
* *
* @param syncResponse the syncResponse * @param syncResponse the syncResponse
*/ */
fun onSyncCompleted(syncResponse: SyncResponse) { override suspend fun onSyncCompleted(syncResponse: SyncResponse) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { // if (syncResponse.deviceLists != null) {
runCatching { // deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
if (syncResponse.deviceLists != null) { // }
deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left) // if (syncResponse.deviceOneTimeKeysCount != null) {
} // val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
if (syncResponse.deviceOneTimeKeysCount != null) { // oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0 // }
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
}
// unwedge if needed // unwedge if needed
try { try {
@ -502,7 +481,13 @@ internal class DefaultCryptoService @Inject constructor(
} }
} }
} }
override fun logDbUsageInfo() {
//
} }
override suspend fun setRoomUnBlacklistUnverifiedDevices(roomId: String) {
cryptoStore.blockUnverifiedDevicesInRoom(roomId, false)
} }
/** /**
@ -512,11 +497,18 @@ internal class DefaultCryptoService @Inject constructor(
* @param algorithm the encryption algorithm. * @param algorithm the encryption algorithm.
* @return the device info, or null if not found / unsupported algorithm / crypto released * @return the device info, or null if not found / unsupported algorithm / crypto released
*/ */
override fun deviceWithIdentityKey(senderKey: String, algorithm: String): CryptoDeviceInfo? { override suspend fun deviceWithIdentityKey(userId: String, senderKey: String, algorithm: String): CryptoDeviceInfo? {
return if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM && algorithm != MXCRYPTO_ALGORITHM_OLM) { return if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM && algorithm != MXCRYPTO_ALGORITHM_OLM) {
// We only deal in olm keys // We only deal in olm keys
null null
} else cryptoStore.deviceWithIdentityKey(senderKey) } else {
withContext(coroutineDispatchers.io) {
cryptoStore.deviceWithIdentityKey(senderKey).takeIf {
// check that the claimed user id matches
it?.userId == userId
}
}
}
} }
/** /**
@ -525,26 +517,32 @@ internal class DefaultCryptoService @Inject constructor(
* @param userId the user id * @param userId the user id
* @param deviceId the device id * @param deviceId the device id
*/ */
override fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? { override suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) { return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
withContext(coroutineDispatchers.io) {
cryptoStore.getUserDevice(userId, deviceId) cryptoStore.getUserDevice(userId, deviceId)
}
} else { } else {
null null
} }
} }
override fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) { // override fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
getDeviceInfoTask // getDeviceInfoTask
.configureWith(GetDeviceInfoTask.Params(deviceId)) { // .configureWith(GetDeviceInfoTask.Params(deviceId)) {
this.executionThread = TaskThread.CRYPTO // this.executionThread = TaskThread.CRYPTO
this.callback = callback // this.callback = callback
} // }
.executeBy(taskExecutor) // .executeBy(taskExecutor)
} // }
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> { override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
return cryptoStore.getUserDeviceList(userId).orEmpty() return cryptoStore.getUserDeviceList(userId).orEmpty()
} }
//
// override fun getCryptoDeviceInfoFlow(userId: String): Flow<List<CryptoDeviceInfo>> {
// return cryptoStore.getUserDeviceListFlow(userId)
// }
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> { override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
return cryptoStore.getLiveDeviceList() return cryptoStore.getLiveDeviceList()
@ -568,7 +566,7 @@ internal class DefaultCryptoService @Inject constructor(
* @param devices the devices. Note that the verified member of the devices in this list will not be updated by this method. * @param devices the devices. Note that the verified member of the devices in this list will not be updated by this method.
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
override fun setDevicesKnown(devices: List<MXDeviceInfo>, callback: MatrixCallback<Unit>?) { fun setDevicesKnown(devices: List<MXDeviceInfo>, callback: MatrixCallback<Unit>?) {
// build a devices map // build a devices map
val devicesIdListByUserId = devices.groupBy({ it.userId }, { it.deviceId }) val devicesIdListByUserId = devices.groupBy({ it.userId }, { it.deviceId })
@ -606,7 +604,7 @@ internal class DefaultCryptoService @Inject constructor(
* @param userId the owner of the device * @param userId the owner of the device
* @param deviceId the unique identifier for the device. * @param deviceId the unique identifier for the device.
*/ */
override fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) { override suspend fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
setDeviceVerificationAction.handle(trustLevel, userId, deviceId) setDeviceVerificationAction.handle(trustLevel, userId, deviceId)
} }
@ -688,8 +686,10 @@ internal class DefaultCryptoService @Inject constructor(
/** /**
* @return the stored device keys for a user. * @return the stored device keys for a user.
*/ */
override fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> { override suspend fun getUserDevices(userId: String): List<CryptoDeviceInfo> {
return cryptoStore.getUserDevices(userId)?.values?.toMutableList() ?: ArrayList() return withContext(coroutineDispatchers.io) {
cryptoStore.getUserDevices(userId)?.values?.toList().orEmpty()
}
} }
private fun isEncryptionEnabledForInvitedUser(): Boolean { private fun isEncryptionEnabledForInvitedUser(): Boolean {
@ -720,14 +720,13 @@ internal class DefaultCryptoService @Inject constructor(
* @param roomId the room identifier the event will be sent. * @param roomId the room identifier the event will be sent.
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
override fun encryptEventContent( override suspend fun encryptEventContent(
eventContent: Content, eventContent: Content,
eventType: String, eventType: String,
roomId: String, roomId: String,
callback: MatrixCallback<MXEncryptEventContentResult> ): MXEncryptEventContentResult {
) {
// moved to crypto scope to have uptodate values // moved to crypto scope to have uptodate values
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { return withContext(coroutineDispatchers.crypto) {
val userIds = getRoomUserIds(roomId) val userIds = getRoomUserIds(roomId)
var alg = roomEncryptorsStore.get(roomId) var alg = roomEncryptorsStore.get(roomId)
if (alg == null) { if (alg == null) {
@ -742,11 +741,9 @@ internal class DefaultCryptoService @Inject constructor(
if (safeAlgorithm != null) { if (safeAlgorithm != null) {
val t0 = clock.epochMillis() val t0 = clock.epochMillis()
Timber.tag(loggerTag.value).v("encryptEventContent() starts") Timber.tag(loggerTag.value).v("encryptEventContent() starts")
runCatching {
val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds) val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms") Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms")
MXEncryptEventContentResult(content, EventType.ENCRYPTED) return@withContext MXEncryptEventContentResult(content, EventType.ENCRYPTED)
}.foldToCallback(callback)
} else { } else {
val algorithm = getEncryptionAlgorithm(roomId) val algorithm = getEncryptionAlgorithm(roomId)
val reason = String.format( val reason = String.format(
@ -754,7 +751,7 @@ internal class DefaultCryptoService @Inject constructor(
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON
) )
Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason") Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason")
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason))) throw Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason))
} }
} }
} }
@ -782,17 +779,6 @@ internal class DefaultCryptoService @Inject constructor(
return internalDecryptEvent(event, timeline) return internalDecryptEvent(event, timeline)
} }
/**
* Decrypt an event asynchronously.
*
* @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @param callback the callback to return data or null
*/
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
eventDecryptor.decryptEventAsync(event, timeline, callback)
}
/** /**
* Decrypt an event. * Decrypt an event.
* *
@ -802,7 +788,7 @@ internal class DefaultCryptoService @Inject constructor(
*/ */
@Throws(MXCryptoError::class) @Throws(MXCryptoError::class)
private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult { private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return eventDecryptor.decryptEvent(event, timeline) return withContext(coroutineDispatchers.crypto) { eventDecryptor.decryptEvent(event, timeline) }
} }
/** /**
@ -862,7 +848,7 @@ internal class DefaultCryptoService @Inject constructor(
* @param event the key event. * @param event the key event.
* @param acceptUnrequested, if true it will force to accept unrequested keys. * @param acceptUnrequested, if true it will force to accept unrequested keys.
*/ */
private fun onRoomKeyEvent(event: Event, acceptUnrequested: Boolean = false) { private suspend fun onRoomKeyEvent(event: Event, acceptUnrequested: Boolean = false) {
val roomKeyContent = event.getDecryptedContent().toModel<RoomKeyContent>() ?: return val roomKeyContent = event.getDecryptedContent().toModel<RoomKeyContent>() ?: return
Timber.tag(loggerTag.value) Timber.tag(loggerTag.value)
.i("onRoomKeyEvent(f:$acceptUnrequested) from: ${event.senderId} type<${event.getClearType()}> , session<${roomKeyContent.sessionId}>") .i("onRoomKeyEvent(f:$acceptUnrequested) from: ${event.senderId} type<${event.getClearType()}> , session<${roomKeyContent.sessionId}>")
@ -918,19 +904,27 @@ internal class DefaultCryptoService @Inject constructor(
): Boolean { ): Boolean {
return when (secretName) { return when (secretName) {
MASTER_KEY_SSSS_NAME -> { MASTER_KEY_SSSS_NAME -> {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
crossSigningService.onSecretMSKGossip(secretValue) crossSigningService.onSecretMSKGossip(secretValue)
}
true true
} }
SELF_SIGNING_KEY_SSSS_NAME -> { SELF_SIGNING_KEY_SSSS_NAME -> {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
crossSigningService.onSecretSSKGossip(secretValue) crossSigningService.onSecretSSKGossip(secretValue)
}
true true
} }
USER_SIGNING_KEY_SSSS_NAME -> { USER_SIGNING_KEY_SSSS_NAME -> {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
crossSigningService.onSecretUSKGossip(secretValue) crossSigningService.onSecretUSKGossip(secretValue)
}
true true
} }
KEYBACKUP_SECRET_SSSS_NAME -> { KEYBACKUP_SECRET_SSSS_NAME -> {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
keysBackupService.onSecretKeyGossip(secretValue) keysBackupService.onSecretKeyGossip(secretValue)
}
true true
} }
else -> false else -> false
@ -943,13 +937,13 @@ internal class DefaultCryptoService @Inject constructor(
* @param roomId the room Id * @param roomId the room Id
* @param event the encryption event. * @param event the encryption event.
*/ */
private fun onRoomEncryptionEvent(roomId: String, event: Event) { private suspend fun onRoomEncryptionEvent(roomId: String, event: Event) {
if (!event.isStateEvent()) { if (!event.isStateEvent()) {
// Ignore // Ignore
Timber.tag(loggerTag.value).w("Invalid encryption event") Timber.tag(loggerTag.value).w("Invalid encryption event")
return return
} }
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { withContext(coroutineDispatchers.io) {
val userIds = getRoomUserIds(roomId) val userIds = getRoomUserIds(roomId)
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds) setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
} }
@ -967,7 +961,7 @@ internal class DefaultCryptoService @Inject constructor(
* @param roomId the room Id * @param roomId the room Id
* @param event the membership event causing the change * @param event the membership event causing the change
*/ */
private fun onRoomMembershipEvent(roomId: String, event: Event) { private suspend fun onRoomMembershipEvent(roomId: String, event: Event) {
// because the encryption event can be after the join/invite in the same batch // because the encryption event can be after the join/invite in the same batch
event.stateKey?.let { _ -> event.stateKey?.let { _ ->
val roomMember: RoomMemberContent? = event.content.toModel() val roomMember: RoomMemberContent? = event.content.toModel()
@ -976,9 +970,8 @@ internal class DefaultCryptoService @Inject constructor(
unrequestedForwardManager.onInviteReceived(roomId, event.senderId.orEmpty(), clock.epochMillis()) unrequestedForwardManager.onInviteReceived(roomId, event.senderId.orEmpty(), clock.epochMillis())
} }
} }
roomEncryptorsStore.get(roomId) ?: /* No encrypting in this room */ return roomEncryptorsStore.get(roomId) ?: /* No encrypting in this room */ return
withContext(coroutineDispatchers.io) {
event.stateKey?.let { userId -> event.stateKey?.let { userId ->
val roomMember: RoomMemberContent? = event.content.toModel() val roomMember: RoomMemberContent? = event.content.toModel()
val membership = roomMember?.membership val membership = roomMember?.membership
@ -997,11 +990,13 @@ internal class DefaultCryptoService @Inject constructor(
} }
} }
} }
}
private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) { private suspend fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) {
if (!event.isStateEvent()) return if (!event.isStateEvent()) return
val eventContent = event.content.toModel<RoomHistoryVisibilityContent>() val eventContent = event.content.toModel<RoomHistoryVisibilityContent>()
val historyVisibility = eventContent?.historyVisibility val historyVisibility = eventContent?.historyVisibility
withContext(coroutineDispatchers.io) {
if (historyVisibility == null) { if (historyVisibility == null) {
cryptoStore.setShouldShareHistory(roomId, false) cryptoStore.setShouldShareHistory(roomId, false)
} else { } else {
@ -1009,6 +1004,7 @@ internal class DefaultCryptoService @Inject constructor(
cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory()) cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory())
} }
} }
}
/** /**
* Upload my user's device keys. * Upload my user's device keys.
@ -1020,19 +1016,39 @@ internal class DefaultCryptoService @Inject constructor(
} }
// Prepare the device keys data to send // Prepare the device keys data to send
// Sign it // Sign it
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary()) val myCryptoDevice = getMyCryptoDevice()
var rest = getMyDevice().toRest() val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myCryptoDevice.signalableJSONDictionary())
var rest = myCryptoDevice.toRest()
rest = rest.copy( rest = rest.copy(
signatures = objectSigner.signObject(canonicalJson) signatures = objectSigner.signObject(canonicalJson)
) )
val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null, null) val keyUploadBody = KeysUploadBody(
deviceKeys = rest,
)
val uploadDeviceKeysParams = UploadKeysTask.Params(keyUploadBody)
uploadKeysTask.execute(uploadDeviceKeysParams) uploadKeysTask.execute(uploadDeviceKeysParams)
cryptoStore.setDeviceKeysUploaded(true) cryptoStore.setDeviceKeysUploaded(true)
} }
override suspend fun receiveSyncChanges(
toDevice: ToDeviceSyncResponse?,
deviceChanges: DeviceListResponse?,
keyCounts: DeviceOneTimeKeysCountSyncResponse?
) {
withContext(coroutineDispatchers.crypto) {
deviceListManager.handleDeviceListsChanges(deviceChanges?.changed.orEmpty(), deviceChanges?.left.orEmpty())
if (keyCounts != null) {
val currentCount = keyCounts.signedCurve25519 ?: 0
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
}
cryptoSyncHandler.handleToDevice(toDevice?.events.orEmpty())
}
}
/** /**
* Export the crypto keys. * Export the crypto keys.
* *
@ -1135,6 +1151,22 @@ internal class DefaultCryptoService @Inject constructor(
} }
} }
override suspend fun downloadKeysIfNeeded(userIds: List<String>, forceDownload: Boolean): MXUsersDevicesMap<CryptoDeviceInfo> {
return deviceListManager.downloadKeys(userIds, forceDownload)
}
override suspend fun getCryptoDeviceInfoList(userId: String): List<CryptoDeviceInfo> {
return cryptoStore.getUserDeviceList(userId).orEmpty()
}
//
// fun getLiveCryptoDeviceInfoList(userId: String): Flow<List<CryptoDeviceInfo>> {
// cryptoStore.getLiveDeviceList(userId).asFlow()
// }
//
// fun getLiveCryptoDeviceInfoList(userIds: List<String>): Flow<List<CryptoDeviceInfo>> {
//
// }
/** /**
* Set the global override for whether the client should ever send encrypted * Set the global override for whether the client should ever send encrypted
* messages to unverified devices. * messages to unverified devices.
@ -1218,11 +1250,11 @@ internal class DefaultCryptoService @Inject constructor(
* *
* @param event the event to decrypt again. * @param event the event to decrypt again.
*/ */
override fun reRequestRoomKeyForEvent(event: Event) { override suspend fun reRequestRoomKeyForEvent(event: Event) {
outgoingKeyRequestManager.requestKeyForEvent(event, true) outgoingKeyRequestManager.requestKeyForEvent(event, true)
} }
override fun requestRoomKeyForEvent(event: Event) { suspend fun requestRoomKeyForEvent(event: Event) {
outgoingKeyRequestManager.requestKeyForEvent(event, false) outgoingKeyRequestManager.requestKeyForEvent(event, false)
} }
@ -1268,12 +1300,8 @@ internal class DefaultCryptoService @Inject constructor(
return unknownDevices return unknownDevices
} }
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>>) { suspend fun downloadKeys(userIds: List<String>, forceDownload: Boolean): MXUsersDevicesMap<CryptoDeviceInfo> {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { return deviceListManager.downloadKeys(userIds, forceDownload)
runCatching {
deviceListManager.downloadKeys(userIds, forceDownload)
}.foldToCallback(callback)
}
} }
override fun addNewSessionListener(newSessionListener: NewSessionListener) { override fun addNewSessionListener(newSessionListener: NewSessionListener) {
@ -1337,8 +1365,8 @@ internal class DefaultCryptoService @Inject constructor(
return cryptoStore.getWithHeldMegolmSession(roomId, sessionId) return cryptoStore.getWithHeldMegolmSession(roomId, sessionId)
} }
override fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>) { override suspend fun prepareToEncrypt(roomId: String) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { withContext(coroutineDispatchers.crypto) {
Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date") Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date")
// Ensure to load all room members // Ensure to load all room members
try { try {
@ -1358,19 +1386,10 @@ internal class DefaultCryptoService @Inject constructor(
if (alg == null) { if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON) val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason") Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason")
callback.onFailure(IllegalArgumentException("Missing algorithm")) throw IllegalArgumentException("Missing algorithm")
return@launch
} }
runCatching {
(alg as? IMXGroupEncryption)?.preshareKey(userIds) (alg as? IMXGroupEncryption)?.preshareKey(userIds)
}.fold(
{ callback.onSuccess(Unit) },
{
Timber.tag(loggerTag.value).e(it, "prepareToEncrypt() failed.")
callback.onFailure(it)
}
)
} }
} }
@ -1398,6 +1417,14 @@ internal class DefaultCryptoService @Inject constructor(
} }
} }
override fun onE2ERoomMemberLoadedFromServer(roomId: String) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
val userIds = getRoomUserIds(roomId)
// Because of LL we might want to update tracked users
deviceListManager.startTrackingDeviceList(userIds)
}
}
/* ========================================================================================== /* ==========================================================================================
* For test only * For test only
* ========================================================================================== */ * ========================================================================================== */

View File

@ -61,7 +61,7 @@ internal class MyDeviceInfoHolder @Inject constructor(
// myDevice.trustLevel = DeviceTrustLevel(crossSigned, true) // myDevice.trustLevel = DeviceTrustLevel(crossSigned, true)
myDevice = CryptoDeviceInfo( myDevice = CryptoDeviceInfo(
credentials.deviceId!!, credentials.deviceId,
credentials.userId, credentials.userId,
keys = keys, keys = keys,
algorithms = MXCryptoAlgorithms.supportedAlgorithms(), algorithms = MXCryptoAlgorithms.supportedAlgorithms(),

View File

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto
import android.content.Context import android.content.Context
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.crypto.model.MXKey import org.matrix.android.sdk.internal.crypto.model.MXKey
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadBody
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
@ -138,7 +139,7 @@ internal class OneTimeKeysUploader @Inject constructor(
private suspend fun fetchOtkCount(): Int? { private suspend fun fetchOtkCount(): Int? {
return tryOrNull("Unable to get OTK count") { return tryOrNull("Unable to get OTK count") {
val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null, null)) val result = uploadKeysTask.execute(UploadKeysTask.Params(KeysUploadBody()))
result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE) result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
} }
} }
@ -227,10 +228,12 @@ internal class OneTimeKeysUploader @Inject constructor(
// For now, we set the device id explicitly, as we may not be using the // For now, we set the device id explicitly, as we may not be using the
// same one as used in login. // same one as used in login.
val uploadParams = UploadKeysTask.Params( val uploadParams = UploadKeysTask.Params(
KeysUploadBody(
deviceKeys = null, deviceKeys = null,
oneTimeKeys = oneTimeJson, oneTimeKeys = oneTimeJson,
fallbackKeys = fallbackJson.takeIf { fallbackJson.isNotEmpty() } fallbackKeys = fallbackJson.takeIf { fallbackJson.isNotEmpty() }
) )
)
return uploadKeysTask.executeRetry(uploadParams, 3) return uploadKeysTask.executeRetry(uploadParams, 3)
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2019 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -74,11 +74,11 @@ internal class RoomDecryptorProvider @Inject constructor(
val alg = when (algorithm) { val alg = when (algorithm) {
MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create().apply { MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create().apply {
this.newSessionListener = object : NewSessionListener { this.newSessionListener = object : NewSessionListener {
override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { override fun onNewSession(roomId: String?, sessionId: String) {
// PR reviewer: the parameter has been renamed so is now in conflict with the parameter of getOrCreateRoomDecryptor // PR reviewer: the parameter has been renamed so is now in conflict with the parameter of getOrCreateRoomDecryptor
newSessionListeners.toList().forEach { newSessionListeners.toList().forEach {
try { try {
it.onNewSession(roomId, senderKey, sessionId) it.onNewSession(roomId, sessionId)
} catch (ignore: Throwable) { } catch (ignore: Throwable) {
} }
} }

View File

@ -28,7 +28,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest
@ -36,7 +35,6 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.util.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@ -153,10 +151,7 @@ internal class SecretShareManager @Inject constructor(
MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master
SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned
USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user
KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey?.toBase64()
?.let {
extractCurveKeyFromRecoveryKey(it)?.toBase64NoPadding()
}
else -> null else -> null
} }
if (secretValue == null) { if (secretValue == null) {
@ -248,7 +243,7 @@ internal class SecretShareManager @Inject constructor(
) )
try { try {
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
sendToDeviceTask.executeRetry(params, 3) sendToDeviceTask.execute(params)
} }
Timber.tag(loggerTag.value) Timber.tag(loggerTag.value)
.d("Secret request sent for $secretName to ${cryptoDeviceInfo.shortDebugString()}") .d("Secret request sent for $secretName to ${cryptoDeviceInfo.shortDebugString()}")

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2019 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -91,10 +91,21 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
} }
// Let's now claim one time keys // Let's now claim one time keys
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim) val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim.map)
val oneTimeKeys = withContext(coroutineDispatchers.io) { val oneTimeKeysForUsers = withContext(coroutineDispatchers.io) {
oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, ONE_TIME_KEYS_RETRY_COUNT) oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, ONE_TIME_KEYS_RETRY_COUNT)
} }
val oneTimeKeys = MXUsersDevicesMap<MXKey>()
for ((userId, mapByUserId) in oneTimeKeysForUsers.oneTimeKeys.orEmpty()) {
for ((deviceId, deviceKey) in mapByUserId) {
val mxKey = MXKey.from(deviceKey)
if (mxKey != null) {
oneTimeKeys.setObject(userId, deviceId, mxKey)
} else {
Timber.e("## claimOneTimeKeysForUsersDevices : fail to create a MXKey")
}
}
}
// let now start olm session using the new otks // let now start olm session using the new otks
devicesToCreateSessionWith.forEach { deviceInfo -> devicesToCreateSessionWith.forEach { deviceInfo ->

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2019 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -57,6 +57,7 @@ internal class MegolmSessionDataImporter @Inject constructor(
progressListener: ProgressListener? progressListener: ProgressListener?
): ImportRoomKeysResult { ): ImportRoomKeysResult {
val t0 = clock.epochMillis() val t0 = clock.epochMillis()
val importedSession = mutableMapOf<String, MutableMap<String, MutableList<String>>>()
val totalNumbersOfKeys = megolmSessionsData.size val totalNumbersOfKeys = megolmSessionsData.size
var lastProgress = 0 var lastProgress = 0
@ -70,18 +71,23 @@ internal class MegolmSessionDataImporter @Inject constructor(
if (null != decrypting) { if (null != decrypting) {
try { try {
val sessionId = megolmSessionData.sessionId val sessionId = megolmSessionData.sessionId ?: return@forEachIndexed
val senderKey = megolmSessionData.senderKey ?: return@forEachIndexed
val roomId = megolmSessionData.roomId ?: return@forEachIndexed
Timber.tag(loggerTag.value).v("## importRoomKeys retrieve senderKey ${megolmSessionData.senderKey} sessionId $sessionId") Timber.tag(loggerTag.value).v("## importRoomKeys retrieve senderKey ${megolmSessionData.senderKey} sessionId $sessionId")
importedSession.getOrPut(roomId) { mutableMapOf() }
.getOrPut(senderKey) { mutableListOf() }
.add(sessionId)
totalNumbersOfImportedKeys++ totalNumbersOfImportedKeys++
// cancel any outstanding room key requests for this session // cancel any outstanding room key requests for this session
Timber.tag(loggerTag.value).d("Imported megolm session $sessionId from backup=$fromBackup in ${megolmSessionData.roomId}") Timber.tag(loggerTag.value).d("Imported megolm session $sessionId from backup=$fromBackup in ${megolmSessionData.roomId}")
outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded( outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded(
megolmSessionData.sessionId ?: "", sessionId,
megolmSessionData.roomId ?: "", roomId,
megolmSessionData.senderKey ?: "", senderKey,
tryOrNull { tryOrNull {
olmInboundGroupSessionWrappers olmInboundGroupSessionWrappers
.firstOrNull { it.session.sessionIdentifier() == megolmSessionData.sessionId } .firstOrNull { it.session.sessionIdentifier() == megolmSessionData.sessionId }
@ -93,7 +99,7 @@ internal class MegolmSessionDataImporter @Inject constructor(
// Have another go at decrypting events sent with this session // Have another go at decrypting events sent with this session
when (decrypting) { when (decrypting) {
is MXMegolmDecryption -> { is MXMegolmDecryption -> {
decrypting.onNewSession(megolmSessionData.roomId, megolmSessionData.senderKey!!, sessionId!!) decrypting.onNewSession(megolmSessionData.roomId, senderKey, sessionId)
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -121,6 +127,6 @@ internal class MegolmSessionDataImporter @Inject constructor(
Timber.tag(loggerTag.value).v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)") Timber.tag(loggerTag.value).v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)")
return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys) return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys, importedSession)
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2020 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -29,7 +29,7 @@ internal class SetDeviceVerificationAction @Inject constructor(
private val defaultKeysBackupService: DefaultKeysBackupService private val defaultKeysBackupService: DefaultKeysBackupService
) { ) {
fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) { suspend fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
val device = cryptoStore.getUserDevice(userId, deviceId) val device = cryptoStore.getUserDevice(userId, deviceId)
// Sanity check // Sanity check

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2019 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -43,5 +43,5 @@ internal interface IMXDecrypting {
* @param defaultKeysBackupService the keys backup service * @param defaultKeysBackupService the keys backup service
* @param forceAccept the keys backup service * @param forceAccept the keys backup service
*/ */
fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService, forceAccept: Boolean = false) {} suspend fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService, forceAccept: Boolean = false) {}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2019 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2020 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2019 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -189,7 +189,7 @@ internal class MXMegolmDecryption(
* @param defaultKeysBackupService the keys backup service * @param defaultKeysBackupService the keys backup service
* @param forceAccept if true will force to accept the forwarded key * @param forceAccept if true will force to accept the forwarded key
*/ */
override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService, forceAccept: Boolean) { override suspend fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService, forceAccept: Boolean) {
Timber.tag(loggerTag.value).v("onRoomKeyEvent(${event.getSenderKey()})") Timber.tag(loggerTag.value).v("onRoomKeyEvent(${event.getSenderKey()})")
var exportFormat = false var exportFormat = false
val roomKeyContent = event.getDecryptedContent()?.toModel<RoomKeyContent>() ?: return val roomKeyContent = event.getDecryptedContent()?.toModel<RoomKeyContent>() ?: return
@ -360,6 +360,6 @@ internal class MXMegolmDecryption(
*/ */
fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey") Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey")
newSessionListener?.onNewSession(roomId, senderKey, sessionId) newSessionListener?.onNewSession(roomId, sessionId)
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2019 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -184,7 +184,9 @@ internal class MXMegolmEncryption(
trusted = true trusted = true
) )
cryptoCoroutineScope.launch {
defaultKeysBackupService.maybeBackupKeys() defaultKeysBackupService.maybeBackupKeys()
}
return MXOutboundSessionInfo( return MXOutboundSessionInfo(
sessionId = sessionId, sessionId = sessionId,

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright 2019 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright 2019 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -21,7 +21,8 @@ import androidx.work.BackoffPolicy
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
@ -35,6 +36,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerif
import org.matrix.android.sdk.api.session.crypto.crosssigning.isLocallyVerified import org.matrix.android.sdk.api.session.crypto.crosssigning.isLocallyVerified
import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified
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.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.fromBase64 import org.matrix.android.sdk.api.util.fromBase64
import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.DeviceListManager
@ -48,8 +50,6 @@ import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.di.WorkManagerProvider
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.TaskThread
import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.util.JsonCanonicalizer import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import org.matrix.android.sdk.internal.util.logLimit import org.matrix.android.sdk.internal.util.logLimit
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
@ -127,8 +127,10 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
// Recover local trust in case private key are there? // Recover local trust in case private key are there?
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
setUserKeysAsTrusted(myUserId, checkUserTrust(myUserId).isVerified()) setUserKeysAsTrusted(myUserId, checkUserTrust(myUserId).isVerified())
} }
}
} catch (e: Throwable) { } catch (e: Throwable) {
// Mmm this kind of a big issue // Mmm this kind of a big issue
Timber.e(e, "Failed to initialize Cross Signing") Timber.e(e, "Failed to initialize Cross Signing")
@ -152,40 +154,30 @@ internal class DefaultCrossSigningService @Inject constructor(
* - Sign the keys and upload them * - Sign the keys and upload them
* - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures. * - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures.
*/ */
override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback<Unit>) { override suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
Timber.d("## CrossSigning initializeCrossSigning") Timber.d("## CrossSigning initializeCrossSigning")
val params = InitializeCrossSigningTask.Params( val params = InitializeCrossSigningTask.Params(
interactiveAuthInterceptor = uiaInterceptor interactiveAuthInterceptor = uiaInterceptor
) )
initializeCrossSigningTask.configureWith(params) { val data = initializeCrossSigningTask
this.callbackThread = TaskThread.CRYPTO .execute(params)
this.callback = object : MatrixCallback<InitializeCrossSigningTask.Result> {
override fun onFailure(failure: Throwable) {
Timber.e(failure, "Error in initializeCrossSigning()")
callback.onFailure(failure)
}
override fun onSuccess(data: InitializeCrossSigningTask.Result) {
val crossSigningInfo = MXCrossSigningInfo( val crossSigningInfo = MXCrossSigningInfo(
myUserId, myUserId,
listOf(data.masterKeyInfo, data.userKeyInfo, data.selfSignedKeyInfo), listOf(data.masterKeyInfo, data.userKeyInfo, data.selfSignedKeyInfo),
true true
) )
withContext(coroutineDispatchers.crypto) {
cryptoStore.setMyCrossSigningInfo(crossSigningInfo) cryptoStore.setMyCrossSigningInfo(crossSigningInfo)
setUserKeysAsTrusted(myUserId, true) setUserKeysAsTrusted(myUserId, true)
cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK) cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK)
crossSigningOlm.masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) } crossSigningOlm.masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) }
crossSigningOlm.userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) } crossSigningOlm.userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) }
crossSigningOlm.selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) } crossSigningOlm.selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) }
callback.onSuccess(Unit)
} }
} }
}.executeBy(taskExecutor)
}
override fun onSecretMSKGossip(mskPrivateKey: String) { override suspend fun onSecretMSKGossip(mskPrivateKey: String) {
Timber.i("## CrossSigning - onSecretSSKGossip") Timber.i("## CrossSigning - onSecretSSKGossip")
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also {
Timber.e("## CrossSigning - onSecretMSKGossip() received secret but public key is not known") Timber.e("## CrossSigning - onSecretMSKGossip() received secret but public key is not known")
@ -212,7 +204,7 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
} }
override fun onSecretSSKGossip(sskPrivateKey: String) { override suspend fun onSecretSSKGossip(sskPrivateKey: String) {
Timber.i("## CrossSigning - onSecretSSKGossip") Timber.i("## CrossSigning - onSecretSSKGossip")
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also {
Timber.e("## CrossSigning - onSecretSSKGossip() received secret but public key is not known") Timber.e("## CrossSigning - onSecretSSKGossip() received secret but public key is not known")
@ -239,7 +231,7 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
} }
override fun onSecretUSKGossip(uskPrivateKey: String) { override suspend fun onSecretUSKGossip(uskPrivateKey: String) {
Timber.i("## CrossSigning - onSecretUSKGossip") Timber.i("## CrossSigning - onSecretUSKGossip")
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also {
Timber.e("## CrossSigning - onSecretUSKGossip() received secret but public key is not knwow ") Timber.e("## CrossSigning - onSecretUSKGossip() received secret but public key is not knwow ")
@ -265,7 +257,7 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
} }
override fun checkTrustFromPrivateKeys( override suspend fun checkTrustFromPrivateKeys(
masterKeyPrivateKey: String?, masterKeyPrivateKey: String?,
uskKeyPrivateKey: String?, uskKeyPrivateKey: String?,
sskPrivateKey: String? sskPrivateKey: String?
@ -328,7 +320,7 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
if (!masterKeyIsTrusted || !userKeyIsTrusted || !selfSignedKeyIsTrusted) { if (!masterKeyIsTrusted || !userKeyIsTrusted || !selfSignedKeyIsTrusted) {
return UserTrustResult.KeysNotTrusted(mxCrossSigningInfo) return UserTrustResult.Failure("Keys not trusted $mxCrossSigningInfo") // UserTrustResult.KeysNotTrusted(mxCrossSigningInfo)
} else { } else {
cryptoStore.markMyMasterKeyAsLocallyTrusted(true) cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
val checkSelfTrust = checkSelfTrust() val checkSelfTrust = checkSelfTrust()
@ -354,18 +346,22 @@ internal class DefaultCrossSigningService @Inject constructor(
* USK * USK
* . * .
*/ */
override fun isUserTrusted(otherUserId: String): Boolean { override suspend fun isUserTrusted(otherUserId: String): Boolean {
return cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() == true return withContext(coroutineDispatchers.io) {
cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() == true
}
} }
override fun isCrossSigningVerified(): Boolean { override suspend fun isCrossSigningVerified(): Boolean {
return checkSelfTrust().isVerified() return withContext(coroutineDispatchers.io) {
checkSelfTrust().isVerified()
}
} }
/** /**
* Will not force a download of the key, but will verify signatures trust chain. * Will not force a download of the key, but will verify signatures trust chain.
*/ */
override fun checkUserTrust(otherUserId: String): UserTrustResult { override suspend fun checkUserTrust(otherUserId: String): UserTrustResult {
Timber.v("## CrossSigning checkUserTrust for $otherUserId") Timber.v("## CrossSigning checkUserTrust for $otherUserId")
if (otherUserId == myUserId) { if (otherUserId == myUserId) {
return checkSelfTrust() return checkSelfTrust()
@ -380,17 +376,17 @@ internal class DefaultCrossSigningService @Inject constructor(
return checkOtherMSKTrusted(myCrossSigningInfo, cryptoStore.getCrossSigningInfo(otherUserId)) return checkOtherMSKTrusted(myCrossSigningInfo, cryptoStore.getCrossSigningInfo(otherUserId))
} }
fun checkOtherMSKTrusted(myCrossSigningInfo: MXCrossSigningInfo?, otherInfo: MXCrossSigningInfo?): UserTrustResult { override fun checkOtherMSKTrusted(myCrossSigningInfo: MXCrossSigningInfo?, otherInfo: MXCrossSigningInfo?): UserTrustResult {
val myUserKey = myCrossSigningInfo?.userKey() val myUserKey = myCrossSigningInfo?.userKey()
?: return UserTrustResult.CrossSigningNotConfigured(myUserId) ?: return UserTrustResult.CrossSigningNotConfigured(myUserId)
if (!myCrossSigningInfo.isTrusted()) { if (!myCrossSigningInfo.isTrusted()) {
return UserTrustResult.KeysNotTrusted(myCrossSigningInfo) return UserTrustResult.Failure("Keys not trusted $myCrossSigningInfo") // UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
} }
// Let's get the other user master key // Let's get the other user master key
val otherMasterKey = otherInfo?.masterKey() val otherMasterKey = otherInfo?.masterKey()
?: return UserTrustResult.UnknownCrossSignatureInfo(otherInfo?.userId ?: "") ?: return UserTrustResult.Failure("Unknown MSK for ${otherInfo?.userId}") // UserTrustResult.UnknownCrossSignatureInfo(otherInfo?.userId ?: "")
val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures
?.get(myUserId) // Signatures made by me ?.get(myUserId) // Signatures made by me
@ -398,7 +394,7 @@ internal class DefaultCrossSigningService @Inject constructor(
if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) { if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) {
Timber.d("## CrossSigning checkUserTrust false for ${otherInfo.userId}, not signed by my UserSigningKey") Timber.d("## CrossSigning checkUserTrust false for ${otherInfo.userId}, not signed by my UserSigningKey")
return UserTrustResult.KeyNotSigned(otherMasterKey) return UserTrustResult.Failure("MSK not signed by my USK $otherMasterKey") // UserTrustResult.KeyNotSigned(otherMasterKey)
} }
// Check that Alice USK signature of Bob MSK is valid // Check that Alice USK signature of Bob MSK is valid
@ -409,7 +405,7 @@ internal class DefaultCrossSigningService @Inject constructor(
otherMasterKey.canonicalSignable() otherMasterKey.canonicalSignable()
) )
} catch (failure: Throwable) { } catch (failure: Throwable) {
return UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey) return UserTrustResult.Failure("Invalid signature $masterKeySignaturesMadeByMyUserKey") // UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey)
} }
return UserTrustResult.Success return UserTrustResult.Success
@ -424,7 +420,7 @@ internal class DefaultCrossSigningService @Inject constructor(
return checkSelfTrust(myCrossSigningInfo, cryptoStore.getUserDeviceList(myUserId)) return checkSelfTrust(myCrossSigningInfo, cryptoStore.getUserDeviceList(myUserId))
} }
fun checkSelfTrust(myCrossSigningInfo: MXCrossSigningInfo?, myDevices: List<CryptoDeviceInfo>?): UserTrustResult { override fun checkSelfTrust(myCrossSigningInfo: MXCrossSigningInfo?, myDevices: List<CryptoDeviceInfo>?): UserTrustResult {
// Special case when it's me, // Special case when it's me,
// I have to check that MSK -> USK -> SSK // I have to check that MSK -> USK -> SSK
// and that MSK is trusted (i know the private key, or is signed by a trusted device) // and that MSK is trusted (i know the private key, or is signed by a trusted device)
@ -473,7 +469,7 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
if (!isMaterKeyTrusted) { if (!isMaterKeyTrusted) {
return UserTrustResult.KeysNotTrusted(myCrossSigningInfo) return UserTrustResult.Failure("Keys not trusted $myCrossSigningInfo") // UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
} }
val myUserKey = myCrossSigningInfo.userKey() val myUserKey = myCrossSigningInfo.userKey()
@ -485,7 +481,7 @@ internal class DefaultCrossSigningService @Inject constructor(
if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
Timber.d("## CrossSigning checkUserTrust false for $myUserId, USK not signed by MSK") Timber.d("## CrossSigning checkUserTrust false for $myUserId, USK not signed by MSK")
return UserTrustResult.KeyNotSigned(myUserKey) return UserTrustResult.Failure("USK not signed by MSK") // UserTrustResult.KeyNotSigned(myUserKey)
} }
// Check that Alice USK signature of Alice MSK is valid // Check that Alice USK signature of Alice MSK is valid
@ -496,7 +492,7 @@ internal class DefaultCrossSigningService @Inject constructor(
myUserKey.canonicalSignable() myUserKey.canonicalSignable()
) )
} catch (failure: Throwable) { } catch (failure: Throwable) {
return UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey) return UserTrustResult.Failure("Invalid MSK signature of USK") // UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey)
} }
val mySSKey = myCrossSigningInfo.selfSigningKey() val mySSKey = myCrossSigningInfo.selfSigningKey()
@ -508,7 +504,7 @@ internal class DefaultCrossSigningService @Inject constructor(
if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
Timber.d("## CrossSigning checkUserTrust false for $myUserId, SSK not signed by MSK") Timber.d("## CrossSigning checkUserTrust false for $myUserId, SSK not signed by MSK")
return UserTrustResult.KeyNotSigned(mySSKey) return UserTrustResult.Failure("SSK not signed by MSK") // UserTrustResult.KeyNotSigned(mySSKey)
} }
// Check that Alice USK signature of Alice MSK is valid // Check that Alice USK signature of Alice MSK is valid
@ -519,26 +515,32 @@ internal class DefaultCrossSigningService @Inject constructor(
mySSKey.canonicalSignable() mySSKey.canonicalSignable()
) )
} catch (failure: Throwable) { } catch (failure: Throwable) {
return UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey) return UserTrustResult.Failure("Invalid signature $ssKeySignaturesMadeByMyMasterKey") // UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey)
} }
return UserTrustResult.Success return UserTrustResult.Success
} }
override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? { override suspend fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
return cryptoStore.getCrossSigningInfo(otherUserId) return withContext(coroutineDispatchers.io) {
cryptoStore.getCrossSigningInfo(otherUserId)
}
} }
override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> { override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
return cryptoStore.getLiveCrossSigningInfo(userId) return cryptoStore.getLiveCrossSigningInfo(userId)
} }
override fun getMyCrossSigningKeys(): MXCrossSigningInfo? { override suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
return cryptoStore.getMyCrossSigningInfo() return withContext(coroutineDispatchers.io) {
cryptoStore.getMyCrossSigningInfo()
}
} }
override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? { override suspend fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
return cryptoStore.getCrossSigningPrivateKeys() return withContext(coroutineDispatchers.io) {
cryptoStore.getCrossSigningPrivateKeys()
}
} }
override fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> { override fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> {
@ -555,24 +557,20 @@ internal class DefaultCrossSigningService @Inject constructor(
cryptoStore.getCrossSigningPrivateKeys()?.allKnown().orFalse() cryptoStore.getCrossSigningPrivateKeys()?.allKnown().orFalse()
} }
override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) { override suspend fun trustUser(otherUserId: String) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { withContext(coroutineDispatchers.crypto) {
Timber.d("## CrossSigning - Mark user $otherUserId as trusted ") Timber.d("## CrossSigning - Mark user $otherUserId as trusted ")
// We should have this user keys // We should have this user keys
val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey() val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey()
if (otherMasterKeys == null) { if (otherMasterKeys == null) {
callback.onFailure(Throwable("## CrossSigning - Other master signing key is not known")) throw Throwable("## CrossSigning - Other master signing key is not known")
return@launch
} }
val myKeys = getUserCrossSigningKeys(myUserId) val myKeys = getUserCrossSigningKeys(myUserId)
if (myKeys == null) { ?: throw Throwable("## CrossSigning - CrossSigning is not setup for this account")
callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account"))
return@launch
}
val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey
if (userPubKey == null || crossSigningOlm.userPkSigning == null) { if (userPubKey == null || crossSigningOlm.userPkSigning == null) {
callback.onFailure(Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey")) throw Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey")
return@launch
} }
// Sign the other MasterKey with our UserSigning key // Sign the other MasterKey with our UserSigning key
@ -580,12 +578,8 @@ internal class DefaultCrossSigningService @Inject constructor(
Map::class.java, Map::class.java,
otherMasterKeys.signalableJSONDictionary() otherMasterKeys.signalableJSONDictionary()
).let { crossSigningOlm.userPkSigning?.sign(it) } ).let { crossSigningOlm.userPkSigning?.sign(it) }
?: // race??
if (newSignature == null) { throw Throwable("## CrossSigning - Failed to sign")
// race??
callback.onFailure(Throwable("## CrossSigning - Failed to sign"))
return@launch
}
cryptoStore.setUserKeysAsTrusted(otherUserId, true) cryptoStore.setUserKeysAsTrusted(otherUserId, true)
@ -593,10 +587,8 @@ internal class DefaultCrossSigningService @Inject constructor(
val uploadQuery = UploadSignatureQueryBuilder() val uploadQuery = UploadSignatureQueryBuilder()
.withSigningKeyInfo(otherMasterKeys.copyForSignature(myUserId, userPubKey, newSignature)) .withSigningKeyInfo(otherMasterKeys.copyForSignature(myUserId, userPubKey, newSignature))
.build() .build()
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
this.executionThread = TaskThread.CRYPTO uploadSignaturesTask.execute(UploadSignaturesTask.Params(uploadQuery))
this.callback = callback
}.executeBy(taskExecutor)
// Local echo for device cross trust, to avoid having to wait for a notification of key change // Local echo for device cross trust, to avoid having to wait for a notification of key change
cryptoStore.getUserDeviceList(otherUserId)?.forEach { device -> cryptoStore.getUserDeviceList(otherUserId)?.forEach { device ->
@ -607,8 +599,8 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
} }
override fun markMyMasterKeyAsTrusted() { override suspend fun markMyMasterKeyAsTrusted() {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { withContext(coroutineDispatchers.crypto) {
cryptoStore.markMyMasterKeyAsLocallyTrusted(true) cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
checkSelfTrust() checkSelfTrust()
// re-verify all trusts // re-verify all trusts
@ -616,35 +608,26 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
} }
override fun trustDevice(deviceId: String, callback: MatrixCallback<Unit>) { override suspend fun trustDevice(deviceId: String) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { withContext(coroutineDispatchers.crypto) {
// This device should be yours // This device should be yours
val device = cryptoStore.getUserDevice(myUserId, deviceId) val device = cryptoStore.getUserDevice(myUserId, deviceId)
if (device == null) { if (device == null) {
callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours")) throw IllegalArgumentException("This device [$deviceId] is not known, or not yours")
return@launch
} }
val myKeys = getUserCrossSigningKeys(myUserId) val myKeys = getUserCrossSigningKeys(myUserId)
if (myKeys == null) { ?: throw Throwable("CrossSigning is not setup for this account")
callback.onFailure(Throwable("CrossSigning is not setup for this account"))
return@launch
}
val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey
if (ssPubKey == null || crossSigningOlm.selfSigningPkSigning == null) { if (ssPubKey == null || crossSigningOlm.selfSigningPkSigning == null) {
callback.onFailure(Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey")) throw Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey")
return@launch
} }
// Sign with self signing // Sign with self signing
val newSignature = crossSigningOlm.selfSigningPkSigning?.sign(device.canonicalSignable()) val newSignature = crossSigningOlm.selfSigningPkSigning?.sign(device.canonicalSignable())
?: throw Throwable("Failed to sign")
if (newSignature == null) {
// race??
callback.onFailure(Throwable("Failed to sign"))
return@launch
}
val toUpload = device.copy( val toUpload = device.copy(
signatures = mapOf( signatures = mapOf(
myUserId myUserId
@ -658,14 +641,16 @@ internal class DefaultCrossSigningService @Inject constructor(
val uploadQuery = UploadSignatureQueryBuilder() val uploadQuery = UploadSignatureQueryBuilder()
.withDeviceInfo(toUpload) .withDeviceInfo(toUpload)
.build() .build()
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { uploadSignaturesTask.execute(UploadSignaturesTask.Params(uploadQuery))
this.executionThread = TaskThread.CRYPTO
this.callback = callback
}.executeBy(taskExecutor)
} }
} }
override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult { override suspend fun shieldForGroup(userIds: List<String>): RoomEncryptionTrustLevel {
// Not used in kotlin SDK?
TODO("Not yet implemented")
}
override suspend fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
val otherDevice = cryptoStore.getUserDevice(otherUserId, otherDeviceId) val otherDevice = cryptoStore.getUserDevice(otherUserId, otherDeviceId)
?: return DeviceTrustResult.UnknownDevice(otherDeviceId) ?: return DeviceTrustResult.UnknownDevice(otherDeviceId)
@ -787,10 +772,12 @@ internal class DefaultCrossSigningService @Inject constructor(
override fun onUsersDeviceUpdate(userIds: List<String>) { override fun onUsersDeviceUpdate(userIds: List<String>) {
Timber.d("## CrossSigning - onUsersDeviceUpdate for users: ${userIds.logLimit()}") Timber.d("## CrossSigning - onUsersDeviceUpdate for users: ${userIds.logLimit()}")
runBlocking {
checkTrustAndAffectedRoomShields(userIds) checkTrustAndAffectedRoomShields(userIds)
} }
}
fun checkTrustAndAffectedRoomShields(userIds: List<String>) { override suspend fun checkTrustAndAffectedRoomShields(userIds: List<String>) {
Timber.d("## CrossSigning - checkTrustAndAffectedRoomShields for users: ${userIds.logLimit()}") Timber.d("## CrossSigning - checkTrustAndAffectedRoomShields for users: ${userIds.logLimit()}")
val workerParams = UpdateTrustWorker.Params( val workerParams = UpdateTrustWorker.Params(
sessionId = sessionId, sessionId = sessionId,
@ -808,7 +795,7 @@ internal class DefaultCrossSigningService @Inject constructor(
.enqueue() .enqueue()
} }
private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) { private suspend fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted) cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
// If it's me, recheck trust of all users and devices? // If it's me, recheck trust of all users and devices?
@ -818,8 +805,11 @@ internal class DefaultCrossSigningService @Inject constructor(
outgoingKeyRequestManager.onSelfCrossSigningTrustChanged(trusted) outgoingKeyRequestManager.onSelfCrossSigningTrustChanged(trusted)
cryptoStore.updateUsersTrust { cryptoStore.updateUsersTrust {
users.add(it) users.add(it)
// called within a real transaction, has to block
runBlocking {
checkUserTrust(it).isVerified() checkUserTrust(it).isVerified()
} }
}
users.forEach { users.forEach {
cryptoStore.getUserDeviceList(it)?.forEach { device -> cryptoStore.getUserDeviceList(it)?.forEach { device ->

View File

@ -15,11 +15,9 @@
*/ */
package org.matrix.android.sdk.internal.crypto.crosssigning package org.matrix.android.sdk.internal.crypto.crosssigning
import android.util.Base64
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
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.internal.util.JsonCanonicalizer import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import timber.log.Timber
internal fun CryptoDeviceInfo.canonicalSignable(): String { internal fun CryptoDeviceInfo.canonicalSignable(): String {
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary()) return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
@ -28,15 +26,3 @@ internal fun CryptoDeviceInfo.canonicalSignable(): String {
internal fun CryptoCrossSigningKey.canonicalSignable(): String { internal fun CryptoCrossSigningKey.canonicalSignable(): String {
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary()) return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
} }
/**
* Decode the base 64. Return null in case of bad format. Should be used when parsing received data from external source
*/
internal fun String.fromBase64Safe(): ByteArray? {
return try {
Base64.decode(this, Base64.DEFAULT)
} catch (throwable: Throwable) {
Timber.e(throwable, "Unable to decode base64 string")
null
}
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2020 The Matrix.org Foundation C.I.C.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,7 +22,9 @@ import com.squareup.moshi.JsonClass
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import io.realm.kotlin.where import io.realm.kotlin.where
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.crosssigning.UserTrustResult import org.matrix.android.sdk.api.session.crypto.crosssigning.UserTrustResult
import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified
@ -68,7 +70,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
val filename: String? = null val filename: String? = null
) : SessionWorkerParams ) : SessionWorkerParams
@Inject lateinit var crossSigningService: DefaultCrossSigningService @Inject lateinit var crossSigningService: CrossSigningService
// It breaks the crypto store contract, but we need to batch things :/ // It breaks the crypto store contract, but we need to batch things :/
@CryptoDatabase @CryptoDatabase
@ -174,9 +176,9 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
?.devices ?.devices
val trustMap = devicesEntities?.associateWith { device -> val trustMap = devicesEntities?.associateWith { device ->
// get up to date from DB has could have been updated runBlocking {
val otherInfo = getCrossSigningInfo(cryptoRealm, userId) crossSigningService.checkDeviceTrust(userId, device.deviceId ?: "", CryptoMapper.mapToModel(device).trustLevel?.locallyVerified)
crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device)) }
} }
// Update trust if needed // Update trust if needed

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoReady import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoReady
/** /**
@ -31,4 +32,6 @@ internal data class KeyVerificationReady(
) : SendToDeviceObject, VerificationInfoReady { ) : SendToDeviceObject, VerificationInfoReady {
override fun toSendToDeviceObject() = this override fun toSendToDeviceObject() = this
override fun toEventContent() = toContent()
} }

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoRequest import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoRequest
/** /**
@ -32,4 +33,6 @@ internal data class KeyVerificationRequest(
) : SendToDeviceObject, VerificationInfoRequest { ) : SendToDeviceObject, VerificationInfoRequest {
override fun toSendToDeviceObject() = this override fun toSendToDeviceObject() = this
override fun toEventContent() = toContent()
} }

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