1
0
mirror of https://github.com/vector-im/riotX-android synced 2025-10-06 16:22:41 +02:00

Compare commits

...

290 Commits

Author SHA1 Message Date
Benoit Marty
cfee2f93f2 Prepare v0.14.2 2020-02-02 14:06:21 +01:00
Benoit Marty
97aca28c0d Merge branch 'release/0.14.2' 2020-02-02 14:05:50 +01:00
Benoit Marty
b158729b53 Prepare v0.14.2 2020-02-02 14:05:12 +01:00
Valere
367057cc29 Fix / cold start 2020-02-02 14:01:45 +01:00
Benoit Marty
5fb4f274f9 Version ++ 2020-02-02 08:00:57 +01:00
Benoit Marty
a35302eae0 Merge branch 'release/0.14.1' 2020-02-02 07:56:00 +01:00
Benoit Marty
6f60f1c6b4 Merge branch 'release/0.14.1' into develop 2020-02-02 07:56:00 +01:00
Benoit Marty
435d8cbc55 Prepare v0.14.1 2020-02-02 07:55:46 +01:00
Benoit Marty
bb0fafcb2f Merge pull request #929 from vector-im/feature/xsigning_fix_4
Feature/xsigning fix 4
2020-02-02 03:13:54 +01:00
Benoit Marty
1d2928b3fb ktlint 2020-02-02 03:13:15 +01:00
Benoit Marty
40b0f60964 Fix issue in dark theme. Also do not limit subtitles to 2 lines 2020-02-02 03:11:27 +01:00
Valere
96a556f449 Fix / Race causing key requests to be sent to early in xsigning 2020-02-02 01:21:08 +01:00
Valere
d436d3b8d4 Move rx logs to verbose 2020-02-02 00:41:36 +01:00
Valere
845f3f5ad1 FIx / use password textedit 2020-02-02 00:04:15 +01:00
Valere
fb838e5407 Fixes #813 2020-02-01 23:24:05 +01:00
Benoit Marty
40c70f64a8 Merge branch 'feature/to_device_done' into develop 2020-02-01 18:48:34 +01:00
Valere
245b3717b9 Send done in toDevice 2020-02-01 18:25:31 +01:00
Benoit Marty
9e15891053 Version++ 2020-02-01 17:21:41 +01:00
Benoit Marty
637eba277f Merge branch 'release/0.14.0' 2020-02-01 17:20:05 +01:00
Benoit Marty
649ebf4a34 Merge branch 'release/0.14.0' into develop 2020-02-01 17:20:04 +01:00
Benoit Marty
e9f220cca2 Prepare release 0.14.0 2020-02-01 17:19:53 +01:00
Benoit Marty
8a9bd97a88 Merge pull request #924 from vector-im/feature/crossing_fix_2
Feature/crossing fix 2
2020-02-01 17:17:30 +01:00
Valere
26400e6372 Prompt before resetting keys 2020-02-01 15:20:04 +01:00
Valere
282be29247 temp shield for read only 2020-02-01 15:20:04 +01:00
Benoit Marty
6ee7ee1460 Fix blink effect 2020-02-01 15:04:22 +01:00
Valere
2e0a84ccc9 display profile faster from known info 2020-02-01 15:03:00 +01:00
Valere
9d6e7d7bd0 Learn more shows my devices when keys not trusted 2020-02-01 14:36:17 +01:00
Valere
8f7b18239d Warn on verify when private keys not known 2020-02-01 14:36:17 +01:00
Benoit Marty
10094a212c ktlint 2020-02-01 14:02:51 +01:00
Benoit Marty
53e36dca9c ZXing 3.3.3 because of https://github.com/zxing/zxing/issues/1170 2020-02-01 13:52:33 +01:00
Benoit Marty
6ec2dd8c00 Add startWith and logs 2020-02-01 12:12:40 +01:00
Benoit Marty
f098d6cf5b Add startWith 2020-02-01 11:45:45 +01:00
Benoit Marty
cd606ba8a1 RoomMember decoration 2020-02-01 11:37:16 +01:00
Benoit Marty
59abee10f8 Convert to ConstraintLayout 2020-02-01 11:37:16 +01:00
Valere
64df9e23c2 Room Profile / Just show e2e status remove learn more 2020-02-01 11:03:00 +01:00
Valere
fc4f5faffd Update Room decoration algo 2020-02-01 10:21:29 +01:00
Benoit Marty
256f8b77aa Add Timber to rx module 2020-02-01 10:09:22 +01:00
Benoit Marty
f2f775cb99 Add TODOs 2020-02-01 01:17:18 +01:00
Benoit Marty
2616a889ef Merge pull request #923 from vector-im/feature/xcrossing_fix
Decoration in room profile and improve Rx flow
2020-01-31 20:48:20 +01:00
Benoit Marty
ccd4c1ed86 ktlint 2020-01-31 20:46:33 +01:00
Benoit Marty
bb92882958 (partially) Fix glitch 2020-01-31 20:28:57 +01:00
Benoit Marty
46dd17644f Room decoration - in room profile UI 2020-01-31 20:12:02 +01:00
Benoit Marty
7e34b2a672 Room decoration - convert to ConstraintLayout 2020-01-31 20:02:01 +01:00
Benoit Marty
c3c88c387b Improve Rx chain and cleanup 2020-01-31 19:55:22 +01:00
Valere
51e0f945a7 Quick Room Decoration 2020-01-31 18:52:33 +01:00
Benoit Marty
fd3619b100 Merge pull request #770 from vector-im/cross_signing
Cross signing
2020-01-31 17:05:18 +01:00
Benoit Marty
e18b9d5155 Room decoration - UI in room list 2020-01-31 16:10:52 +01:00
Benoit Marty
f5ecf4bd90 Room decoration - UI 2020-01-31 15:02:54 +01:00
Valere
27c74c9118 live device in settings 2020-01-31 14:42:00 +01:00
Benoit Marty
4d91bc934b Fix ktlint 2020-01-31 14:12:03 +01:00
Valere
5c547794f2 Merge branch 'develop' into cross_signing 2020-01-31 14:09:40 +01:00
Benoit Marty
43358cd86c Make self verification work! 2020-01-31 12:18:27 +01:00
Benoit Marty
87b76d10dd Format 2020-01-31 11:11:27 +01:00
Benoit Marty
80f4f95f81 QRCode: requestId is not supposed to be an eventId 2020-01-31 11:11:27 +01:00
Valere
8e5c7239cf Settings Sessions / Now live + support devices with no keys 2020-01-31 10:33:53 +01:00
Valere
7b385c3b36 Quick copy change device -> session 2020-01-31 10:00:44 +01:00
Valere
4fb59aadb1 Fix / ensure RoomKeyRequest are made after device is verified 2020-01-31 09:39:31 +01:00
Valere
850c830e1f Fix / auto ready request when waiting bottomsheet on screen 2020-01-31 09:16:03 +01:00
Benoit Marty
9dde43f65b PR Review: var -> val, internal and other cleanup 2020-01-30 23:40:25 +01:00
Benoit Marty
9d566e9352 Merge pull request #918 from vector-im/feature/sort_room_members
Sort room members by display names
2020-01-30 21:06:40 +01:00
Benoit Marty
eb46a4949f Merge pull request #920 from duncanturk/patch-1
Add "get it on F-Droid"
2020-01-30 21:05:53 +01:00
Benoit Marty
506c2dd262 Merge pull request #921 from vector-im/decorations
Add field in QR code and make some optional
2020-01-30 18:48:30 +01:00
Benoit Marty
ccd857016c ktlint 2020-01-30 18:46:08 +01:00
Benoit Marty
225e4e0433 To Device Verification Request 2020-01-30 18:43:50 +01:00
Benoit Marty
069bd3c258 use isMe 2020-01-30 16:59:22 +01:00
Benoit Marty
fb98d6ef42 QRCode: add other_device_key field and make it optional, along with other_user_key 2020-01-30 16:46:12 +01:00
Benoit Marty
6282f81bc4 Remove typo 2020-01-30 16:46:12 +01:00
Valere
a5ca2b1d34 Fix / incoming start verif popup should not show when bottomsheet there 2020-01-30 16:36:13 +01:00
Valere
50d5ad3625 Self verification + toDevice Request 2020-01-30 16:35:42 +01:00
Valere
03c5e61b2e Fix / post merge 2020-01-30 16:35:06 +01:00
Valere
4ddd831d7f Prepare support for toDevice .request 2020-01-30 16:11:34 +01:00
Valere
ff95392e10 Fix / Refresh trust state on own keys/device trust change 2020-01-30 16:10:59 +01:00
Benoit Marty
e2c2c2418c Merge pull request #919 from vector-im/qr_step_validate
Qr step validate
2020-01-30 14:14:16 +01:00
Christopher Rossbach
e44dc347c6 Add "get it on F-Droid"
RiotX is on F-Droid now
2020-01-30 12:07:33 +01:00
Benoit Marty
fbd0bbc575 Improve clarity of the algorithm to enable encryption for DMs 2020-01-30 11:24:05 +01:00
ganfra
b848d0530f Update realm to 6.1.0: should fix some of the native crashes 2020-01-30 11:04:37 +01:00
Benoit Marty
2bccd19f84 QRcode: Url encode the keys 2020-01-30 10:17:04 +01:00
Benoit Marty
2111daea52 Add a step to confirm that other user has scanned the SR code 2020-01-30 10:09:59 +01:00
Benoit Marty
57a13fa30d Sort room members by display names 2020-01-29 18:07:57 +01:00
Benoit Marty
c4649a5824 Merge pull request #916 from vector-im/debug_qr
Negotiate E2E by default for DMs (#907)
2020-01-29 18:02:43 +01:00
Benoit Marty
6f6c3184dd Avoid test if previous result is null 2020-01-29 17:13:41 +01:00
Benoit Marty
d7feb6dd5c Merge pull request #913 from vector-im/feature/rainbow
Rainbow
2020-01-29 16:35:34 +01:00
Benoit Marty
e6c3f7c77b Nicer API 2020-01-29 16:26:19 +01:00
Benoit Marty
8b6ffc2fb1 ktlint 2020-01-29 16:18:33 +01:00
Benoit Marty
ae36846aaf Negotiate E2E by default for DMs (#907) 2020-01-29 16:11:23 +01:00
Benoit Marty
237da5bb16 No need to have mutable list in param 2020-01-29 16:03:28 +01:00
Benoit Marty
a4abe5f552 Set timeout to 60s when creating a room 2020-01-29 16:00:07 +01:00
Benoit Marty
e1ddde5501 Make CreateRoomParams a regular data class 2020-01-29 14:23:32 +01:00
Valere
754ca3c582 Fix / fail to update usk when dl own keys 2020-01-29 14:00:02 +01:00
Benoit Marty
70b04dbaea Disable not passing test, to avoid waiting too long when running the test suite 2020-01-29 12:36:38 +01:00
Benoit Marty
b44b6726ed Also update the tests 2020-01-29 12:29:19 +01:00
Benoit Marty
80ec199135 Convert VerificationTxState to a sealed class 2020-01-29 12:00:49 +01:00
Benoit Marty
305dfd4c7a Merge pull request #911 from vector-im/feature/encryption_notice
Modify encryption notice
2020-01-29 10:32:23 +01:00
Benoit Marty
73bb4bb785 Merge pull request #912 from vector-im/feature/e2e_opt_out
e2e opt in when creating room (not DM)
2020-01-29 10:29:33 +01:00
Valere
1d84ccd64a Merge pull request #881 from vector-im/xsigning_sdk
Xsigning sdk
2020-01-29 10:26:46 +01:00
Valere
63e36b0403 Remove unused test 2020-01-29 10:16:57 +01:00
Valere
2c568b4de9 clean klint 2020-01-29 09:59:09 +01:00
Benoit Marty
27fe4e3680 Fix build and add tests 2020-01-29 09:44:53 +01:00
Benoit Marty
007b0cabf2 Add a few TUs 2020-01-28 22:43:10 +01:00
Benoit Marty
b2338dfcd3 Make the TU passes 2020-01-28 22:35:40 +01:00
Benoit Marty
6d7d4993a6 Add TUs for RainbowGenerator (not all passing) 2020-01-28 21:56:02 +01:00
Benoit Marty
da9b9f4864 Make the whole cell clickable 2020-01-28 21:31:02 +01:00
Benoit Marty
ef0b438a89 Give the possibility to enable encryption when creating room (#837) 2020-01-28 21:31:02 +01:00
Valere
9a79297e14 Merge pull request #910 from vector-im/qr_code_step_2
Qr code step 2
2020-01-28 18:11:09 +01:00
Valere
a57393cafa More log + quick fix in settings 2020-01-28 18:09:17 +01:00
Benoit Marty
e12de3fba0 Merge pull request #891 from vector-im/feature/event-unknown
Feature/event unknown
2020-01-28 17:30:02 +01:00
Benoit Marty
2eeeea3377 Encryption is enabled only for MEGOLM. 2020-01-28 17:19:22 +01:00
Benoit Marty
976a8fc568 Hide the algorithm when turning on e2e (#897) 2020-01-28 16:36:28 +01:00
Benoit Marty
b7ecfd997d Fix compilation issue after rebase 2020-01-28 16:02:20 +01:00
Benoit Marty
e0b3ea7e48 QrCode: WIP 2020-01-28 15:55:44 +01:00
Benoit Marty
9c829e62e6 QrCode: WIP 2020-01-28 15:55:44 +01:00
Benoit Marty
20c7e4c3ad QrCode: improve UI 2020-01-28 15:55:44 +01:00
Benoit Marty
69ab5e43d5 QrCode: WIP 2020-01-28 15:55:02 +01:00
Benoit Marty
f46023e84c QrCode: WIP 2020-01-28 15:55:02 +01:00
Benoit Marty
d8d465f70b QrCode: WIP 2020-01-28 15:53:57 +01:00
Benoit Marty
8659216955 QrCode: WIP 2020-01-28 15:53:57 +01:00
Benoit Marty
39e746413a QrCode: WIP 2020-01-28 15:53:57 +01:00
Benoit Marty
0aaba26f17 Rename classes 2020-01-28 15:53:57 +01:00
Benoit Marty
fc04833157 Rename parameter 2020-01-28 15:53:57 +01:00
Benoit Marty
f80861bed8 Add TODO 2020-01-28 15:53:57 +01:00
Benoit Marty
9e796067cc Do not support SHOW or SCAN if cross-signing is not enabled 2020-01-28 15:53:57 +01:00
Benoit Marty
fb5148fd43 Avoid to inject credential (again) 2020-01-28 15:52:17 +01:00
Benoit Marty
be77017209 Avoid injecting credentials. Inject userId and deviceId instead
And cleanup API
2020-01-28 15:48:09 +01:00
Benoit Marty
962b85b041 Add TODO 2020-01-28 15:48:09 +01:00
Benoit Marty
adc2d570eb QR code: handle the case where other user can scan QR codes 2020-01-28 15:48:09 +01:00
Benoit Marty
df49ab8362 QR code: update code which build URL 2020-01-28 15:48:09 +01:00
Benoit Marty
efc8cfb9a1 QR code: modify APIs 2020-01-28 15:48:09 +01:00
Benoit Marty
345824daa2 Keep on renaming 2020-01-28 15:48:09 +01:00
Benoit Marty
050eb0af9d Create dedicated View and Epoxy item for QrCode 2020-01-28 15:48:09 +01:00
Valere
ca4ed6e1bd Fix / Error management and clear keys 2020-01-28 15:35:11 +01:00
Valere
f021f8110d post merge 2020-01-28 12:04:19 +01:00
Valere
109ff4f908 Merge branch 'cross_signing' into xsigning_sdk 2020-01-28 11:33:54 +01:00
Valere
c9f0209ebf post merge fix 2020-01-28 11:23:37 +01:00
Valere
7daa088618 Merge branch 'develop' into cross_signing 2020-01-28 11:13:31 +01:00
Valere
83e44ac96e Fix / cross signing info live data not always updated 2020-01-28 11:02:12 +01:00
Benoit Marty
f3e88c75cf Fix typo 2020-01-28 09:42:56 +01:00
Valere
ea6e8a6789 Basic debug screen to setup keys 2020-01-27 23:51:08 +01:00
Valere
6cece03998 Profile detailed device info + verify manually 2020-01-27 17:55:00 +01:00
Valere
08ae0b485a Profile Screen / Add show device list trust screen 2020-01-27 09:25:58 +01:00
Valere
665c577747 SDK / update trust on key change + live method in Service 2020-01-27 09:25:16 +01:00
Valere
d60351bcb7 Verify from RoomMember Profile 2020-01-24 19:15:23 +01:00
Valere
a758efc018 Renamed room transport classes 2020-01-24 11:32:24 +01:00
Valere
d0addc4c4f Refactored Verification Classes 2020-01-24 11:29:26 +01:00
Valere
bb5179140c Update profile screen for xSigning 2020-01-24 09:14:32 +01:00
Benoit Marty
e9ea69f055 Add support for /rainbow and /rainbowme command (#879) 2020-01-23 23:34:21 +01:00
Valere
e47791f290 Merge pull request #888 from vector-im/qr_code
Qr code
2020-01-23 16:41:23 +01:00
Valere
91ae96a153 QuickFix / Do not verify yourself in dm 2020-01-23 16:16:57 +01:00
Valere
0148949a4f Fix / prevent verification toaster to show when in good room 2020-01-23 16:13:46 +01:00
Valere
65cb812fc6 Fix / Unknown transaction when started by other after request 2020-01-23 16:04:29 +01:00
Valere
e8a4f1fb90 Fix: .cancel won't appear in debug show all 2020-01-23 16:04:03 +01:00
Benoit Marty
632832a651 Nearly same code for DefaultItem and NoticeItem 2020-01-23 15:44:41 +01:00
Benoit Marty
d530c64a84 Render defaultItem as other item: display user avatar
Also ensure bottom sheet always has a header, for user avatar and date
2020-01-23 15:35:46 +01:00
Benoit Marty
426e291ce9 i18n for RiotX limitation messages 2020-01-23 14:46:36 +01:00
Valere
1276d1f39d Update My device list + action to verify 2020-01-23 13:57:17 +01:00
Benoit Marty
4a1012cf81 Add TODOs 2020-01-23 11:48:08 +01:00
Benoit Marty
5819790c1b Distinguish Show SR code and Scan QR code capability 2020-01-23 11:25:44 +01:00
Benoit Marty
b3089343ad Support SCAN method (WIP) 2020-01-23 10:47:29 +01:00
Benoit Marty
37b950897f Base64 no wrap and extension for the reverse operation 2020-01-23 10:17:07 +01:00
Benoit Marty
d2fab91e9d Improve code 2020-01-22 19:08:21 +01:00
Benoit Marty
c323b61575 Ignore typo 2020-01-22 18:27:59 +01:00
Benoit Marty
0e55f81879 Ensure all is escaped properly 2020-01-22 18:26:34 +01:00
Benoit Marty
cbf418c401 Update after MSC change 2020-01-22 18:22:01 +01:00
Benoit Marty
41c691f26c Create QrCodeData class and method to convert to URL and vice versa, with TUs 2020-01-22 17:58:25 +01:00
Benoit Marty
81337d1624 Also keep the same parameter order: (userId, deviceId) to avoid silly errors 2020-01-22 17:00:16 +01:00
Benoit Marty
79df6b8402 Start plugin QR code to the code 2020-01-22 15:56:43 +01:00
Benoit Marty
537b1be0c5 Update wording 2020-01-22 15:26:26 +01:00
Benoit Marty
c1259161e5 QRCode: generate and scan QRCodes 2020-01-22 15:03:56 +01:00
Benoit Marty
75e39535bc Merge pull request #883 from vector-im/feature/share
Improve the room list when sharing to RiotX
2020-01-22 11:49:50 +01:00
Benoit Marty
c971f18fc0 Add section header when displaying room list to share (#771) 2020-01-22 11:49:30 +01:00
Benoit Marty
3c2fa40b58 Sharing things to RiotX: sort list by recent room first (#771) 2020-01-22 11:49:04 +01:00
Benoit Marty
0b74863c6d Merge pull request #877 from vector-im/feature/version_name
F-Droid: fix the "-dev" issue in version name (#815)
2020-01-22 11:45:36 +01:00
Benoit Marty
dca950140d Merge branch 'develop' into feature/version_name 2020-01-22 11:45:27 +01:00
Benoit Marty
a13a78ccc0 Merge pull request #874 from vector-im/feature/authors
Builds repoductibility and Authors file
2020-01-22 11:43:51 +01:00
Benoit Marty
7327dc97b4 Merge pull request #876 from vector-im/feature/room_settings
Room settings, and enable encryption in unencrypted rooms (#212)
2020-01-22 11:40:26 +01:00
Benoit Marty
da3e547d82 Create facility extension to observe ViewEvents 2020-01-21 15:08:49 +01:00
Valere
a0aa1f34d3 Quick Fix todevice verif broken
Added dbg screen for cross signing
2020-01-21 14:58:06 +01:00
Benoit Marty
e81c804ed6 Ensure when will be exhaustive 2020-01-21 12:51:28 +01:00
Benoit Marty
be371f9279 Introduce ViewEvents in ViewModel and code harmonization 2020-01-21 12:51:28 +01:00
Valere
a6364f0be5 remove dead code 2020-01-21 10:25:57 +01:00
Valere
390879e3fd Added check self keys + force DL after initialize Xsigning 2020-01-21 10:25:57 +01:00
Valere
6ab540045b Refactoring / deprecation of MXDeviceInfo
introduced TrustLevels
2020-01-21 10:25:57 +01:00
Valere
98ba2d39a8 SAS verif, support signing and verification of Cross Signing 2020-01-21 10:25:57 +01:00
Valere
859c75df98 Initial commit 2020-01-21 10:25:57 +01:00
Valere
ea9166e0c6 Merge pull request #811 from vector-im/verification_toasters
[Cross Signing DM Verif]  Basic Incoming request toast + cleaning
2020-01-21 10:21:24 +01:00
Benoit Marty
d672313649 Room settings: use boolean instead of Async 2020-01-20 17:41:29 +01:00
Benoit Marty
f5e6b4b857 F-Droid: fix the "-dev" issue in version name (#815) 2020-01-20 16:08:57 +01:00
Benoit Marty
28db05e509 Cleanup and copy wording from Riot-Web 2020-01-20 15:39:14 +01:00
Benoit Marty
56b140fcb4 Room settings: rename stuff for genericity 2020-01-20 15:23:40 +01:00
Benoit Marty
eee6969b02 Room settings, and enable encryption in unencrypted rooms (#212) 2020-01-20 15:11:21 +01:00
Benoit Marty
825427b7b0 Add a first content into the AUTHORS file 2020-01-17 16:50:18 +01:00
Benoit Marty
8c32796d5c Ensure builds are reproducible (#842) 2020-01-17 16:06:58 +01:00
Valere
e45c1e6c2a Fix / post merge issues 2020-01-17 15:17:16 +01:00
Benoit Marty
188cd6beff Verification: improve blinking effect (not perfect yet) 2020-01-17 15:05:06 +01:00
Benoit Marty
aaeb54db7c Verification: Introduce VerificationMethod enum 2020-01-17 15:05:06 +01:00
Benoit Marty
92f26bc20a Verification: migrate to Epoxy - Cleanup 2020-01-17 15:05:06 +01:00
Benoit Marty
fc22b7988f Verification: migrate to Epoxy - Add missing icons 2020-01-17 15:05:06 +01:00
Benoit Marty
878bae1c45 Verification: migrate to Epoxy - Choose Fragment 2020-01-17 15:05:06 +01:00
Benoit Marty
a8e81d95cf Verification: migrate to Epoxy - Disable item animation 2020-01-17 15:05:06 +01:00
Benoit Marty
cd1665a8e8 Verification: migrate to Epoxy - Conclusion 2020-01-17 15:05:06 +01:00
Benoit Marty
7170471686 Verification: migrate to Epoxy - create sub packages 2020-01-17 15:05:06 +01:00
Benoit Marty
98020404ff Verification: migrate to Epoxy - rename package 2020-01-17 15:05:06 +01:00
Benoit Marty
b2348427bd Verification: migrate to Epoxy - Emoji Fragment 2020-01-17 15:05:06 +01:00
Benoit Marty
832df59b58 Verification: migrate to Epoxy - Request Fragment 2020-01-17 15:05:06 +01:00
Benoit Marty
32689facc5 Verification: migrate to Epoxy - Request Fragment 2020-01-17 15:05:06 +01:00
Benoit Marty
d3071e5816 Rename layout for consistency 2020-01-17 15:04:36 +01:00
Benoit Marty
83a6f564c3 Ensure BottomSheets call ButterKnife unbinder 2020-01-17 15:04:36 +01:00
Benoit Marty
3b420dbb50 typo 2020-01-17 15:04:36 +01:00
Benoit Marty
9fe155bafd postWork now returns the Cancellable 2020-01-17 15:04:36 +01:00
Benoit Marty
3c982866d8 Restore lost Fragment binding... 2020-01-17 15:04:36 +01:00
Benoit Marty
494ad83704 Inject WorkManagerProvider, to avoid injecting the Android context
Also ensure WorkManager uses a distinct tags for each session (for future multi-sessions support)
2020-01-17 15:04:36 +01:00
Benoit Marty
4543658ae0 Extends SessionWorkerParams 2020-01-17 15:04:36 +01:00
Benoit Marty
689fd1ea90 Fix issue with SessionId for the worker
Also rename some variables
2020-01-17 15:04:36 +01:00
Valere
a95410c118 fix rebase 2020-01-17 15:04:36 +01:00
Valere
8749e49e80 Basic Incoming request toast + cleaning 2020-01-17 15:04:36 +01:00
Valere
8400ab6efe Merge branch 'develop' into cross_signing 2020-01-17 14:57:08 +01:00
Benoit Marty
d1699279fe Version++ 2020-01-17 14:25:25 +01:00
Benoit Marty
115058124c Merge branch 'release/0.13.0' into develop 2020-01-17 14:24:10 +01:00
Valere
a7c948815c Merge branch 'develop' into cross_signing 2020-01-14 12:31:29 +01:00
Valere
7354eab061 Post merge fixes 2020-01-11 10:16:09 +01:00
Valere
fb9abefe59 Merge branch 'develop' into cross_signing 2020-01-10 18:38:54 +01:00
Valere
06b41af467 Merge pull request #793 from vector-im/outgoing_dm_verif
BottomSheet UX for verification
2020-01-06 10:22:27 +01:00
Valere
c2cd149299 Fix / accept button was not starting the verify sheet
Was launching start sheet, because request was not known by VerificationService. Due to message observer blocked trying to download keys..
2020-01-03 19:06:23 +01:00
Valere
08ed8d4fa7 Code review 2020-01-03 17:38:33 +01:00
Valere
d1233e8470 Fix / tap on accept shows request button instead of start 2020-01-02 17:04:41 +01:00
Valere
bf28f14b8b Fix / Decline request was not implemented 2020-01-02 16:13:13 +01:00
Valere
52c25b803f cleaning 2020-01-02 15:16:45 +01:00
Valere
b26318f15c Fix / Cancel messages was not sent 2020-01-02 12:51:12 +01:00
Valere
f541661059 Use workers to send verification messages 2020-01-02 11:52:27 +01:00
Valere
5b210df7c5 Manage done states + cleaning 2019-12-31 10:36:10 +01:00
Valere
935b3d7f3f cleaning 2019-12-30 20:18:08 +01:00
Valere
3c4506cb58 merge madness ?? 2019-12-30 19:52:48 +01:00
Valere
3eed9b5083 cleaning 2019-12-30 18:42:32 +01:00
Valere
6bf3a703df BottomSheet UX 2019-12-30 18:01:06 +01:00
Valere
2152af8851 klint 2019-12-30 17:54:44 +01:00
Valere
5b33a42f8a FIx / missing strings after merge 2019-12-30 17:36:33 +01:00
Valere
a73cd61b96 WIP 2019-12-30 15:16:11 +01:00
Valere
4edd5e3530 Added SAS do not match api 2019-12-30 14:32:58 +01:00
Valere
4c0cbca4cb Support .verification.ready event 2019-12-30 14:32:58 +01:00
Valere
308b15b908 Fix / m.key.verification.key was not sent in toDevice mode 2019-12-30 14:32:04 +01:00
Valere
38906084d1 WIP 2019-12-30 14:32:04 +01:00
Valere
0997d9abf4 Merge branch 'develop' into cross_signing 2019-12-30 14:27:47 +01:00
Valere
94125a0215 Merge branch 'develop' into cross_signing 2019-12-19 10:15:47 +01:00
Valere
d97402f757 Merge pull request #767 from vector-im/dm_verif_incoming_timeline
Dm verif incoming timeline
2019-12-19 10:12:55 +01:00
Valere
08d005a611 fix merge 2019-12-16 15:44:32 +01:00
Valere
89b414e8fe Merge branch 'develop' into dm_verif_incoming_timeline 2019-12-16 15:30:39 +01:00
Valere
3727affc15 cleaning 2019-12-13 18:00:04 +01:00
Valere
ff5305ee66 Fix / Verification Msg show fallback text in room summary 2019-12-13 17:56:06 +01:00
Valere
3953022258 Merge branch 'cross_signing' into dm_verif_incoming_timeline 2019-12-13 16:51:53 +01:00
Valere
b473aeb475 Merge pull request #765 from vector-im/sdk_reference_aggregation
Aggregate Event References for DM verifications
2019-12-13 16:45:27 +01:00
Valere
289c03e724 Code review 2019-12-13 16:41:55 +01:00
Valere
210dcca0ee Fix / Handling multi open sessions 2019-12-13 11:22:39 +01:00
Valere
872baacfe4 Fix / verification conclusion not showing in non hidden mode 2019-12-13 11:20:19 +01:00
Valere
975de1dbed Cleaning / klint 2019-12-12 18:48:57 +01:00
Valere
dedc622140 Merge branch 'sdk_reference_aggregation' into dm_verif_incoming_timeline 2019-12-12 15:55:01 +01:00
Valere
9842cac504 More explicit val naming 2019-12-12 15:29:56 +01:00
Valere
35404b9a7f Fix merge 2019-12-12 15:05:13 +01:00
Valere
0afcb60e7d fix rebase 2019-12-12 14:31:01 +01:00
Valere
cb595177a9 Fix test compilation 2019-12-12 13:37:17 +01:00
Valere
cb4d52c9fb Aggregate Event References for DM verifications 2019-12-12 13:37:17 +01:00
Valere
d0a3b4663e FIx / room transport was not updating state 2019-12-12 13:37:17 +01:00
Valere
f53b99a423 rebase 2019-12-12 13:37:17 +01:00
Valere
6da0693488 Convert KeyVerificationStart to data class 2019-12-12 13:37:17 +01:00
Valere
ea817ff1c5 fix rebase 2019-12-12 12:04:38 +01:00
Valere
62f0c6edc0 Fix / Use transport to start verification 2019-12-12 12:04:38 +01:00
Valere
e71ad0e515 Simple strategy to Ignore old verification messages 2019-12-12 12:03:59 +01:00
Valere
553604423e Support verification using room transport 2019-12-12 12:03:42 +01:00
Valere
be723256d3 FIx / room transport was not updating state 2019-12-12 10:49:42 +01:00
Valere
819d7182bb rebase 2019-12-12 10:49:42 +01:00
Valere
0a2ffdbdf1 Convert KeyVerificationStart to data class 2019-12-12 10:49:42 +01:00
Valere
4ac7331f3d fix rebase 2019-12-12 10:49:42 +01:00
Valere
7fc57bdf9b Fix / Use transport to start verification 2019-12-12 10:49:42 +01:00
Valere
d370f6d7c8 Simple strategy to Ignore old verification messages 2019-12-12 10:49:42 +01:00
Valere
46ef442139 cleaning 2019-12-12 10:49:42 +01:00
Benoit Marty
5f3dc73440 Code review 2019-12-12 10:49:42 +01:00
Valere
6137a88a6f Support verification using room transport 2019-12-12 10:49:42 +01:00
Valere
82af848c33 Fix / Verification Request Local Echo 2019-12-12 10:27:58 +01:00
Valere
a673bf092d Show untrusted conclusions 2019-12-11 18:19:32 +01:00
Valere
0776a301ea Incoming DM verification handling in timeline 2019-12-11 16:49:34 +01:00
Valere
02f03e6b23 Fix test compilation 2019-12-11 16:00:53 +01:00
Valere
8305ce67dd Aggregate Event References for DM verifications 2019-12-11 14:44:31 +01:00
Valere
95deeb1be7 Merge pull request #730 from vector-im/feature/sdk_dm_verification
[SDK] MSC2241 / verification in DMs
2019-12-11 11:10:46 +01:00
Valere
73f0132d5d FIx / room transport was not updating state 2019-12-10 16:37:54 +01:00
Valere
c462d15bcf rebase 2019-12-10 14:23:56 +01:00
Valere
3cdd373368 Convert KeyVerificationStart to data class 2019-12-10 11:14:52 +01:00
Valere
e14602d1dc fix rebase 2019-12-10 11:14:52 +01:00
Valere
2aa9c3ea22 Fix / Use transport to start verification 2019-12-10 11:14:52 +01:00
Valere
bbd9738452 Simple strategy to Ignore old verification messages 2019-12-10 11:14:52 +01:00
Valere
36c5566b07 cleaning 2019-12-10 11:14:52 +01:00
Benoit Marty
ce63332a2f Code review 2019-12-10 11:14:52 +01:00
Valere
26b4b6e194 Support verification using room transport 2019-12-10 11:14:20 +01:00
458 changed files with 19019 additions and 4183 deletions

2
.gitignore vendored
View File

@@ -15,3 +15,5 @@
ktlint
.idea/copyright/New_vector.xml
.idea/copyright/profiles_settings.xml
.idea/copyright/New_Vector_Ltd.xml

View File

@@ -1,5 +1,6 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View File

@@ -9,6 +9,8 @@
<w>decryptor</w>
<w>emoji</w>
<w>emojis</w>
<w>fdroid</w>
<w>gplay</w>
<w>hmac</w>
<w>ktlint</w>
<w>linkified</w>

View File

@@ -0,0 +1,35 @@
A full developer contributors list can be found [here](https://github.com/vector-im/riotX-android/graphs/contributors).
# Core team:
Even if we try to be able to work on all the functionalities, we have more knowledge about what we have developed ourselves.
## Benoit: Android team leader
[@benoit.marty:matrix.org](https://matrix.to/#/@benoit.marty:matrix.org)
- Android team leader and project leader, Android developer, GitHub community manager.
- Specialist of the account creation, and many other fun features.
- Reviewing and polishing developed features, code quality manager, PRs reviewer, GitHub community manager.
- Release manager on the Play Store
## François: Software architect
[@ganfra:matrix.org](https://matrix.to/#/@ganfra:matrix.org)
- Software architect, Android developer
- First developer on the project.
- Work mainly on the global architecture of the project.
- Specialist of the timeline, and lots of other features.
## Valere: Product manager, Android developer
[@valere35:matrix.org](https://matrix.to/#/@valere35:matrix.org)
- Product manager, Android developer
- Specialist on the crypto implementation.
# Other contributors
First of all, we thank all contributors who use RiotX and report problems on this GitHub project or via the integrated rageshake function.
We do not forget all translators, for their work of translating RiotX into many languages. They are also the authors of RiotX.
Feel free to add your name below, when you contribute to the project!

View File

@@ -1,3 +1,35 @@
Changes in RiotX 0.14.2 (2020-02-02)
===================================================
Fix RiotX not starting issue
Changes in RiotX 0.14.1 (2020-02-02)
===================================================
Bugfix 🐛:
- Cross-signing: fix UX issue when closing the bottom sheet verification (#813)
- Room and room member profile: fix issues on dark and black themes
Changes in RiotX 0.14.0 (2020-02-01)
===================================================
Features ✨:
- First implementation of Cross-signing
- Enable encryption in unencrypted rooms, from the room settings (#212)
- Negotiate E2E by default for DMs (#907)
Improvements 🙌:
- Sharing things to RiotX: sort list by recent room first (#771)
- Hide the algorithm when turning on e2e (#897)
- Sort room members by display names
Other changes:
- Add support for /rainbow and /rainbowme commands (#879)
Build 🧱:
- Ensure builds are reproducible (#842)
- F-Droid: fix the "-dev" issue in version name (#815)
Changes in RiotX 0.13.0 (2020-01-17)
===================================================
@@ -95,6 +127,7 @@ Changes in RiotX 0.9.0 (2019-12-05)
Features ✨:
- Account creation. It's now possible to create account on any homeserver with RiotX (#34)
- Iteration of the login flow (#613)
- [SDK] MSC2241 / verification in DMs (#707)
Improvements 🙌:
- Send mention Pills from composer
@@ -300,7 +333,7 @@ Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-a
=======================================================
Changes in RiotX 0.0.0 (2020-XX-XX)
Changes in RiotX 0.X.0 (2020-XX-XX)
===================================================
Features ✨:

View File

@@ -12,12 +12,13 @@ RiotX is an Android Matrix Client currently in beta but in active development.
It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-android) with a new user experience. RiotX will become the official replacement as soon as all features are implemented.
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.riotx)
[<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/app/im.vector.riotx)
Nightly build: [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
# New Android SDK
RiotX is based on a new Android SDK fully written in Kotlin (like RiotX). In order to make the early development as fast as possible, RiotX and the new SDK currently share the same git repository. We will make separate repos once the API is stable enough.
RiotX is based on a new Android SDK fully written in Kotlin (like RiotX). In order to make the early development as fast as possible, RiotX and the new SDK currently share the same git repository. We will make separate repos once the SDK is stable enough.
# Roadmap

View File

@@ -41,6 +41,9 @@ dependencies {
// Paging
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
// Logging
implementation 'com.jakewharton.timber:timber:4.7.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

View File

@@ -16,6 +16,8 @@
package im.vector.matrix.rx
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
@@ -28,19 +30,99 @@ import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.functions.BiFunction
import timber.log.Timber
class RxRoom(private val room: Room) {
class RxRoom(private val room: Room, private val session: Session) {
fun liveRoomSummary(): Observable<Optional<RoomSummary>> {
return room.getRoomSummaryLive().asObservable()
val summaryObservable = room.getRoomSummaryLive()
.asObservable()
.startWith(room.roomSummary().toOptional())
.doOnNext { Timber.v("RX: summary emitted for: ${it.getOrNull()?.roomId}") }
val memberIdsChangeObservable = summaryObservable
.map {
it.getOrNull()?.let { roomSummary ->
if (roomSummary.isEncrypted) {
// Return the list of other users
roomSummary.otherMemberIds + listOf(session.myUserId)
} else {
// Return an empty list, the room is not encrypted
emptyList()
}
}.orEmpty()
}.distinctUntilChanged()
.doOnNext { Timber.v("RX: memberIds emitted. Size: ${it.size}") }
// Observe the device info of the users in the room
val cryptoDeviceInfoObservable = memberIdsChangeObservable
.switchMap { membersIds ->
session.getLiveCryptoDeviceInfo(membersIds)
.asObservable()
.map {
// If any key change, emit the userIds list
membersIds
}
.startWith(membersIds)
.doOnNext { Timber.v("RX: CryptoDeviceInfo emitted. Size: ${it.size}") }
}
.doOnNext { Timber.v("RX: cryptoDeviceInfo emitted 2. Size: ${it.size}") }
val roomEncryptionTrustLevelObservable = cryptoDeviceInfoObservable
.map { userIds ->
if (userIds.isEmpty()) {
Optional<RoomEncryptionTrustLevel>(null)
} else {
session.getCrossSigningService().getTrustLevelForUsers(userIds).toOptional()
}
}
.doOnNext { Timber.v("RX: roomEncryptionTrustLevel emitted: ${it.getOrNull()?.name}") }
return Observable
.combineLatest<Optional<RoomSummary>, Optional<RoomEncryptionTrustLevel>, Optional<RoomSummary>>(
summaryObservable,
roomEncryptionTrustLevelObservable,
BiFunction { summary, level ->
summary.getOrNull()?.copy(
roomEncryptionTrustLevel = level.getOrNull()
).toOptional()
}
)
.doOnNext { Timber.v("RX: final room summary emitted for ${it.getOrNull()?.roomId}") }
}
fun liveRoomMembers(queryParams: RoomMemberQueryParams): Observable<List<RoomMemberSummary>> {
return room.getRoomMembersLive(queryParams).asObservable()
val roomMembersObservable = room.getRoomMembersLive(queryParams).asObservable()
.startWith(room.getRoomMembers(queryParams))
.doOnNext { Timber.v("RX: room members emitted. Size: ${it.size}") }
// TODO Do it only for room members of the room (switchMap)
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
.startWith(emptyList<CryptoDeviceInfo>())
.doOnNext { Timber.v("RX: cryptoDeviceInfo emitted. Size: ${it.size}") }
return Observable
.combineLatest<List<RoomMemberSummary>, List<CryptoDeviceInfo>, List<RoomMemberSummary>>(
roomMembersObservable,
cryptoDeviceInfoObservable,
BiFunction { summaries, _ ->
summaries.map {
if (room.isEncrypted()) {
it.copy(
// Get the trust level of a virtual room with only this user
userEncryptionTrustLevel = session.getCrossSigningService().getTrustLevelForUsers(listOf(it.userId))
)
} else {
it
}
}
}
)
.doOnNext { Timber.v("RX: final room members emitted. Size: ${it.size}") }
}
fun liveAnnotationSummary(eventId: String): Observable<Optional<EventAnnotationsSummary>> {
@@ -88,6 +170,6 @@ class RxRoom(private val room: Room) {
}
}
fun Room.rx(): RxRoom {
return RxRoom(this)
fun Room.rx(session: Session): RxRoom {
return RxRoom(this, session)
}

View File

@@ -18,6 +18,7 @@ package im.vector.matrix.rx
import androidx.paging.PagedList
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
import im.vector.matrix.android.api.session.group.GroupSummaryQueryParams
import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.pushers.Pusher
@@ -29,14 +30,41 @@ import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.functions.BiFunction
import timber.log.Timber
class RxSession(private val session: Session) {
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Observable<List<RoomSummary>> {
return session.getRoomSummariesLive(queryParams).asObservable()
val summariesObservable = session.getRoomSummariesLive(queryParams).asObservable()
.startWith(session.getRoomSummaries(queryParams))
.doOnNext { Timber.v("RX: summaries emitted: size: ${it.size}") }
val cryptoDeviceInfoObservable = session.getLiveCryptoDeviceInfo().asObservable()
.startWith(emptyList<CryptoDeviceInfo>())
.doOnNext { Timber.v("RX: crypto device info emitted: size: ${it.size}") }
return Observable
.combineLatest<List<RoomSummary>, List<CryptoDeviceInfo>, List<RoomSummary>>(
summariesObservable,
cryptoDeviceInfoObservable,
BiFunction { summaries, _ ->
summaries.map {
if (it.isEncrypted) {
it.copy(
roomEncryptionTrustLevel = session.getCrossSigningService()
.getTrustLevelForUsers(it.otherMemberIds + session.myUserId)
)
} else {
it
}
}
}
)
.doOnNext { Timber.d("RX: final summaries emitted: size: ${it.size}") }
}
fun liveGroupSummaries(queryParams: GroupSummaryQueryParams): Observable<List<GroupSummary>> {
@@ -98,6 +126,15 @@ class RxSession(private val session: Session) {
fun getProfileInfo(userId: String): Single<JsonDict> = singleBuilder {
session.getProfile(userId, it)
}
fun liveUserCryptoDevices(userId: String): Observable<List<CryptoDeviceInfo>> {
return session.getLiveCryptoDeviceInfo(userId).asObservable()
}
fun liveCrossSigningInfo(userId: String): Observable<Optional<MXCrossSigningInfo>> {
return session.getCrossSigningService().getLiveCrossSigningKeys(userId).asObservable()
.startWith(session.getCrossSigningService().getUserCrossSigningKeys(userId).toOptional())
}
}
fun Session.rx(): RxSession {

View File

@@ -10,7 +10,7 @@ buildscript {
jcenter()
}
dependencies {
classpath "io.realm:realm-gradle-plugin:6.0.2"
classpath "io.realm:realm-gradle-plugin:6.1.0"
}
}
@@ -74,7 +74,7 @@ android {
}
static def gitRevision() {
def cmd = "git rev-parse --short HEAD"
def cmd = "git rev-parse --short=8 HEAD"
return cmd.execute().text.trim()
}

View File

@@ -21,6 +21,7 @@ import androidx.test.core.app.ApplicationProvider
import java.io.File
interface InstrumentedTest {
fun context(): Context {
return ApplicationProvider.getApplicationContext()
}

View File

@@ -18,5 +18,8 @@ package im.vector.matrix.android
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.Executors
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main)
internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(Main, Main, Main, Main,
Executors.newSingleThreadExecutor().asCoroutineDispatcher())

View File

@@ -19,6 +19,7 @@ package im.vector.matrix.android.common
import android.content.Context
import android.net.Uri
import androidx.lifecycle.Observer
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.MatrixConfiguration
@@ -31,8 +32,16 @@ import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
import org.junit.Assert.*
import java.util.*
import im.vector.matrix.android.api.session.sync.SyncState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import java.util.ArrayList
import java.util.UUID
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@@ -73,23 +82,25 @@ class CommonTestHelper(context: Context) {
* @param session the session to sync
*/
fun syncSession(session: Session) {
// val lock = CountDownLatch(1)
// val observer = androidx.lifecycle.Observer<SyncState> { syncState ->
// if (syncState is SyncState.Idle) {
// lock.countDown()
// }
// }
// TODO observe?
// while (session.syncState().value !is SyncState.Idle) {
// sleep(100)
// }
val lock = CountDownLatch(1)
session.open()
session.startSync(true)
// await(lock)
// session.syncState().removeObserver(observer)
val syncLiveData = runBlocking(Dispatchers.Main) {
session.getSyncStateLive()
}
val syncObserver = object : Observer<SyncState> {
override fun onChanged(t: SyncState?) {
if (session.hasAlreadySynced()) {
lock.countDown()
syncLiveData.removeObserver(this)
}
}
}
GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) }
await(lock)
}
/**
@@ -272,7 +283,7 @@ class CommonTestHelper(context: Context) {
fun signout(session: Session) {
val lock = CountDownLatch(1)
session.signOut(true, object : TestMatrixCallback<Unit>(lock) {})
session.signOut(true, TestMatrixCallback(lock))
await(lock)
}
}

View File

@@ -17,11 +17,15 @@
package im.vector.matrix.android.common
import android.os.SystemClock
import androidx.lifecycle.Observer
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.TimelineSettings
@@ -29,8 +33,16 @@ import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
import org.junit.Assert.*
import java.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import java.util.Arrays
import java.util.HashMap
import java.util.concurrent.CountDownLatch
class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
@@ -49,7 +61,7 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
var roomId: String? = null
val lock1 = CountDownLatch(1)
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, object : TestMatrixCallback<String>(lock1) {
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), object : TestMatrixCallback<String>(lock1) {
override fun onSuccess(data: String) {
roomId = data
super.onSuccess(data)
@@ -78,26 +90,31 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
val aliceSession = cryptoTestData.firstSession
val aliceRoomId = cryptoTestData.roomId
val room = aliceSession.getRoom(aliceRoomId)!!
val aliceRoom = aliceSession.getRoom(aliceRoomId)!!
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams)
val lock1 = CountDownLatch(2)
// val bobEventListener = object : MXEventListener() {
// override fun onNewRoom(roomId: String) {
// if (TextUtils.equals(roomId, aliceRoomId)) {
// if (!statuses.containsKey("onNewRoom")) {
// statuses["onNewRoom"] = "onNewRoom"
// lock1.countDown()
// }
// }
// }
// }
//
// bobSession.dataHandler.addListener(bobEventListener)
val bobRoomSummariesLive = runBlocking(Dispatchers.Main) {
bobSession.getRoomSummariesLive(roomSummaryQueryParams { })
}
room.invite(bobSession.myUserId, callback = object : TestMatrixCallback<Unit>(lock1) {
val newRoomObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(t: List<RoomSummary>?) {
if (t?.isNotEmpty() == true) {
statuses["onNewRoom"] = "onNewRoom"
lock1.countDown()
bobRoomSummariesLive.removeObserver(this)
}
}
}
GlobalScope.launch(Dispatchers.Main) {
bobRoomSummariesLive.observeForever(newRoomObserver)
}
aliceRoom.invite(bobSession.myUserId, callback = object : TestMatrixCallback<Unit>(lock1) {
override fun onSuccess(data: Unit) {
statuses["invite"] = "invite"
super.onSuccess(data)
@@ -108,25 +125,25 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) {
assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom"))
// bobSession.dataHandler.removeListener(bobEventListener)
val lock2 = CountDownLatch(2)
bobSession.joinRoom(aliceRoomId, callback = TestMatrixCallback(lock2))
val roomJoinedObserver = object : Observer<List<RoomSummary>> {
override fun onChanged(t: List<RoomSummary>?) {
if (bobSession.getRoom(aliceRoomId)
?.getRoomMember(aliceSession.myUserId)
?.membership == Membership.JOIN) {
statuses["AliceJoin"] = "AliceJoin"
lock2.countDown()
bobRoomSummariesLive.removeObserver(this)
}
}
}
// room.addEventListener(object : MXEventListener() {
// override fun onLiveEvent(event: Event, roomState: RoomState) {
// if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_STATE_ROOM_MEMBER)) {
// val contentToConsider = event.contentAsJsonObject
// val member = JsonUtils.toRoomMember(contentToConsider)
//
// if (TextUtils.equals(member.membership, RoomMember.MEMBERSHIP_JOIN)) {
// statuses["AliceJoin"] = "AliceJoin"
// lock2.countDown()
// }
// }
// }
// })
GlobalScope.launch(Dispatchers.Main) {
bobRoomSummariesLive.observeForever(roomJoinedObserver)
}
bobSession.joinRoom(aliceRoomId, callback = TestMatrixCallback(lock2))
mTestHelper.await(lock2)

View File

@@ -0,0 +1,209 @@
package im.vector.matrix.android.internal.crypto.crosssigning
import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.CryptoTestHelper
import im.vector.matrix.android.common.SessionTestParams
import im.vector.matrix.android.common.TestConstants
import im.vector.matrix.android.common.TestMatrixCallback
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
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.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class XSigningTest : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
@Test
fun test_InitializeAndStoreKeys() {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val aliceLatch = CountDownLatch(1)
aliceSession.getCrossSigningService()
.initializeCrossSigning(UserPasswordAuth(
user = aliceSession.myUserId,
password = TestConstants.PASSWORD
), TestMatrixCallback(aliceLatch))
mTestHelper.await(aliceLatch)
val myCrossSigningKeys = aliceSession.getCrossSigningService().getMyCrossSigningKeys()
val masterPubKey = myCrossSigningKeys?.masterKey()
assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey)
val selfSigningKey = myCrossSigningKeys?.selfSigningKey()
assertNotNull("SelfSigned key should be stored", selfSigningKey?.unpaddedBase64PublicKey)
val userKey = myCrossSigningKeys?.userKey()
assertNotNull("User key should be stored", userKey?.unpaddedBase64PublicKey)
assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted() == true)
assertTrue("Signing Keys should be trusted", aliceSession.getCrossSigningService().checkUserTrust(aliceSession.myUserId).isVerified())
mTestHelper.signout(aliceSession)
}
@Test
fun test_CrossSigningCheckBobSeesTheKeys() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceAuthParams = UserPasswordAuth(
user = aliceSession.myUserId,
password = TestConstants.PASSWORD
)
val bobAuthParams = UserPasswordAuth(
user = bobSession!!.myUserId,
password = TestConstants.PASSWORD
)
val latch = CountDownLatch(2)
aliceSession.getCrossSigningService().initializeCrossSigning(aliceAuthParams, TestMatrixCallback(latch))
bobSession.getCrossSigningService().initializeCrossSigning(bobAuthParams, TestMatrixCallback(latch))
mTestHelper.await(latch)
// Check that alice can see bob keys
val downloadLatch = CountDownLatch(1)
aliceSession.downloadKeys(listOf(bobSession.myUserId), true, TestMatrixCallback(downloadLatch))
mTestHelper.await(downloadLatch)
val bobKeysFromAlicePOV = aliceSession.getCrossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey())
assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey())
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, bobSession.getCrossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey)
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, bobSession.getCrossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey)
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
mTestHelper.signout(aliceSession)
mTestHelper.signout(bobSession)
}
@Test
fun test_CrossSigningTestAliceTrustBobNewDevice() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceAuthParams = UserPasswordAuth(
user = aliceSession.myUserId,
password = TestConstants.PASSWORD
)
val bobAuthParams = UserPasswordAuth(
user = bobSession!!.myUserId,
password = TestConstants.PASSWORD
)
val latch = CountDownLatch(2)
aliceSession.getCrossSigningService().initializeCrossSigning(aliceAuthParams, TestMatrixCallback(latch))
bobSession.getCrossSigningService().initializeCrossSigning(bobAuthParams, TestMatrixCallback(latch))
mTestHelper.await(latch)
// Check that alice can see bob keys
val downloadLatch = CountDownLatch(1)
val bobUserId = bobSession.myUserId
aliceSession.downloadKeys(listOf(bobUserId), true, TestMatrixCallback(downloadLatch))
mTestHelper.await(downloadLatch)
val bobKeysFromAlicePOV = aliceSession.getCrossSigningService().getUserCrossSigningKeys(bobUserId)
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
val trustLatch = CountDownLatch(1)
aliceSession.getCrossSigningService().trustUser(bobUserId, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
trustLatch.countDown()
}
override fun onFailure(failure: Throwable) {
fail("Failed to trust bob")
}
})
mTestHelper.await(trustLatch)
// 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
val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true))
val bobSecondDeviceId = bobSession2.sessionParams.credentials.deviceId
// Check that bob first session sees the new login
val bobKeysLatch = CountDownLatch(1)
bobSession.downloadKeys(listOf(bobUserId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
override fun onFailure(failure: Throwable) {
fail("Failed to get device")
}
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) {
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId!!) == false) {
fail("Bob should see the new device")
}
bobKeysLatch.countDown()
}
})
mTestHelper.await(bobKeysLatch)
val bobSecondDevicePOVFirstDevice = bobSession.getDeviceInfo(bobUserId, bobSecondDeviceId)
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
// Manually mark it as trusted from first session
val bobSignLatch = CountDownLatch(1)
bobSession.getCrossSigningService().signDevice(bobSecondDeviceId!!, object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
bobSignLatch.countDown()
}
override fun onFailure(failure: Throwable) {
fail("Failed to trust bob ${failure.localizedMessage}")
}
})
mTestHelper.await(bobSignLatch)
// Now alice should cross trust bob's second device
val aliceKeysLatch = CountDownLatch(1)
aliceSession.downloadKeys(listOf(bobUserId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
override fun onFailure(failure: Throwable) {
fail("Failed to get device")
}
override fun onSuccess(data: MXUsersDevicesMap<CryptoDeviceInfo>) {
// check that the device is seen
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
fail("Alice should see the new device")
}
aliceKeysLatch.countDown()
}
})
mTestHelper.await(aliceKeysLatch)
val result = aliceSession.getCrossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId, null)
assertTrue("Bob second device should be trusted from alice POV", result.isCrossSignedVerified())
mTestHelper.signout(aliceSession)
mTestHelper.signout(bobSession)
mTestHelper.signout(bobSession2)
}
}

View File

@@ -25,23 +25,36 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
import im.vector.matrix.android.common.*
import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.CryptoTestData
import im.vector.matrix.android.common.CryptoTestHelper
import im.vector.matrix.android.common.SessionTestParams
import im.vector.matrix.android.common.TestConstants
import im.vector.matrix.android.common.TestMatrixCallback
import im.vector.matrix.android.common.assertDictEquals
import im.vector.matrix.android.common.assertListEquals
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
import im.vector.matrix.android.internal.crypto.MegolmSessionData
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.keysbackup.model.KeysBackupVersionTrust
import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
import org.junit.Assert.*
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.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import java.util.*
import java.util.ArrayList
import java.util.Collections
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@@ -298,7 +311,11 @@ class KeysBackupTest : InstrumentedTest {
val decryption = keysBackup.pkDecryptionFromRecoveryKey(keyBackupCreationInfo.recoveryKey)
assertNotNull(decryption)
// - Check decryptKeyBackupData() returns stg
val sessionData = keysBackup.decryptKeyBackupData(keyBackupData, session.olmInboundGroupSession!!.sessionIdentifier(), cryptoTestData.roomId, decryption!!)
val sessionData = keysBackup
.decryptKeyBackupData(keyBackupData,
session.olmInboundGroupSession!!.sessionIdentifier(),
cryptoTestData.roomId,
decryption!!)
assertNotNull(sessionData)
// - Compare the decrypted megolm key with the original one
assertKeysEquals(session.exportKeys(), sessionData)
@@ -1161,7 +1178,7 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup2.isEnabled)
// - Validate the old device from the new one
aliceSession2.setDeviceVerification(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED, oldDeviceId, aliceSession2.myUserId)
aliceSession2.setDeviceVerification(DeviceTrustLevel(false, true), aliceSession2.myUserId, oldDeviceId)
// -> Backup should automatically enable on the new device
val latch4 = CountDownLatch(1)

View File

@@ -19,22 +19,34 @@ package im.vector.matrix.android.internal.crypto.verification
import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.crypto.sas.*
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.SasMode
import im.vector.matrix.android.api.session.crypto.sas.VerificationMethod
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
import im.vector.matrix.android.api.session.crypto.sas.VerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.VerificationTxState
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.CryptoTestHelper
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
import org.junit.Assert.*
import im.vector.matrix.android.internal.crypto.model.rest.toValue
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.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import java.util.*
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@@ -50,53 +62,57 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceSasMgr = aliceSession.getSasVerificationService()
val bobSasMgr = bobSession!!.getSasVerificationService()
val aliceVerificationService = aliceSession.getVerificationService()
val bobVerificationService = bobSession!!.getVerificationService()
val bobTxCreatedLatch = CountDownLatch(1)
val bobListener = object : SasVerificationService.SasVerificationListener {
override fun transactionCreated(tx: SasVerificationTransaction) {}
val bobListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
override fun transactionUpdated(tx: SasVerificationTransaction) {
override fun transactionUpdated(tx: VerificationTransaction) {
bobTxCreatedLatch.countDown()
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
bobSasMgr.addListener(bobListener)
bobVerificationService.addListener(bobListener)
val txID = aliceSasMgr.beginKeyVerificationSAS(bobSession.myUserId, bobSession.getMyDevice().deviceId)
val txID = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS,
bobSession.myUserId,
bobSession.getMyDevice().deviceId,
null)
assertNotNull("Alice should have a started transaction", txID)
val aliceKeyTx = aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID!!)
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
assertNotNull("Alice should have a started transaction", aliceKeyTx)
mTestHelper.await(bobTxCreatedLatch)
bobSasMgr.removeListener(bobListener)
bobVerificationService.removeListener(bobListener)
val bobKeyTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID)
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
assertNotNull("Bob should have started verif transaction", bobKeyTx)
assertTrue(bobKeyTx is SASVerificationTransaction)
assertTrue(bobKeyTx is SASDefaultVerificationTransaction)
assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
assertTrue(aliceKeyTx is SASVerificationTransaction)
assertTrue(aliceKeyTx is SASDefaultVerificationTransaction)
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
val aliceSasTx = aliceKeyTx as SASVerificationTransaction?
val bobSasTx = bobKeyTx as SASVerificationTransaction?
val aliceSasTx = aliceKeyTx as SASDefaultVerificationTransaction?
val bobSasTx = bobKeyTx as SASDefaultVerificationTransaction?
assertEquals("Alice state should be started", SasVerificationTxState.Started, aliceSasTx!!.state)
assertEquals("Bob state should be started by alice", SasVerificationTxState.OnStarted, bobSasTx!!.state)
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 : SasVerificationService.SasVerificationListener {
override fun transactionCreated(tx: SasVerificationTransaction) {}
val bobListener2 = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
override fun transactionUpdated(tx: SasVerificationTransaction) {
override fun transactionUpdated(tx: VerificationTransaction) {
if (tx.transactionId == txID) {
if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnCancelled) {
val immutableState = (tx as SASDefaultVerificationTransaction).state
if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
cancelLatch.countDown()
}
}
@@ -104,29 +120,32 @@ class SASTest : InstrumentedTest {
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
bobSasMgr.addListener(bobListener2)
bobVerificationService.addListener(bobListener2)
aliceSasTx.cancel(CancelCode.User)
mTestHelper.await(cancelLatch)
assertEquals("Should be cancelled on alice side",
SasVerificationTxState.Cancelled, aliceSasTx.state)
assertEquals("Should be cancelled on bob side",
SasVerificationTxState.OnCancelled, bobSasTx.state)
assertTrue("Should be cancelled on alice side", aliceSasTx.state is VerificationTxState.Cancelled)
assertTrue("Should be cancelled on bob side", bobSasTx.state is VerificationTxState.Cancelled)
assertEquals("Should be User cancelled on alice side",
CancelCode.User, aliceSasTx.cancelledReason)
assertEquals("Should be User cancelled on bob side",
CancelCode.User, aliceSasTx.cancelledReason)
val aliceCancelState = aliceSasTx.state as VerificationTxState.Cancelled
val bobCancelState = bobSasTx.state as VerificationTxState.Cancelled
assertNull(bobSasMgr.getExistingTransaction(aliceSession.myUserId, txID))
assertNull(aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID))
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))
cryptoTestData.close()
}
@Test
fun test_key_agreement_protocols_must_include_curve25519() {
fail("Not passing for the moment")
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
@@ -135,8 +154,23 @@ class SASTest : InstrumentedTest {
val tid = "00000000"
// Bob should receive a cancel
var canceledToDeviceEvent: Event? = null
var cancelReason: CancelCode? = null
val cancelLatch = CountDownLatch(1)
val bobListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
override fun transactionUpdated(tx: VerificationTransaction) {
if (tx.transactionId == tid && tx.state is VerificationTxState.Cancelled) {
cancelReason = (tx.state as VerificationTxState.Cancelled).cancelCode
cancelLatch.countDown()
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
bobSession.getVerificationService().addListener(bobListener)
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
// TODO override fun onToDeviceEvent(event: Event?) {
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
@@ -152,31 +186,31 @@ class SASTest : InstrumentedTest {
val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.getMyDevice().deviceId
val aliceListener = object : SasVerificationService.SasVerificationListener {
override fun transactionCreated(tx: SasVerificationTransaction) {}
val aliceListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
override fun transactionUpdated(tx: SasVerificationTransaction) {
if ((tx as IncomingSASVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
(tx as IncomingSASVerificationTransaction).performAccept()
override fun transactionUpdated(tx: VerificationTransaction) {
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
(tx as IncomingSasVerificationTransaction).performAccept()
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
aliceSession.getSasVerificationService().addListener(aliceListener)
aliceSession.getVerificationService().addListener(aliceListener)
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
mTestHelper.await(cancelLatch)
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
cryptoTestData.close()
}
@Test
fun test_key_agreement_macs_Must_include_hmac_sha256() {
fail("Not passing for the moment")
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
@@ -214,6 +248,7 @@ class SASTest : InstrumentedTest {
@Test
fun test_key_agreement_short_code_include_decimal() {
fail("Not passing for the moment")
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val bobSession = cryptoTestData.secondSession!!
@@ -253,18 +288,19 @@ class SASTest : InstrumentedTest {
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()
startMessage.fromDevice = bobSession.getMyDevice().deviceId
startMessage.method = KeyVerificationStart.VERIF_METHOD_SAS
startMessage.transactionID = tid
startMessage.keyAgreementProtocols = protocols
startMessage.hashes = hashes
startMessage.messageAuthenticationCodes = mac
startMessage.shortAuthenticationStrings = codes
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.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)
@@ -287,31 +323,31 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceSasMgr = aliceSession.getSasVerificationService()
val aliceVerificationService = aliceSession.getVerificationService()
val aliceCreatedLatch = CountDownLatch(2)
val aliceCancelledLatch = CountDownLatch(2)
val createdTx = ArrayList<SASVerificationTransaction>()
val aliceListener = object : SasVerificationService.SasVerificationListener {
override fun transactionCreated(tx: SasVerificationTransaction) {
createdTx.add(tx as SASVerificationTransaction)
val createdTx = mutableListOf<SASDefaultVerificationTransaction>()
val aliceListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {
createdTx.add(tx as SASDefaultVerificationTransaction)
aliceCreatedLatch.countDown()
}
override fun transactionUpdated(tx: SasVerificationTransaction) {
if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnCancelled) {
override fun transactionUpdated(tx: VerificationTransaction) {
if ((tx as SASDefaultVerificationTransaction).state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) {
aliceCancelledLatch.countDown()
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
aliceSasMgr.addListener(aliceListener)
aliceVerificationService.addListener(aliceListener)
val bobUserId = bobSession!!.myUserId
val bobDeviceId = bobSession.getMyDevice().deviceId
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
mTestHelper.await(aliceCreatedLatch)
mTestHelper.await(aliceCancelledLatch)
@@ -329,46 +365,46 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceSasMgr = aliceSession.getSasVerificationService()
val bobSasMgr = bobSession!!.getSasVerificationService()
val aliceVerificationService = aliceSession.getVerificationService()
val bobVerificationService = bobSession!!.getVerificationService()
var accepted: KeyVerificationAccept? = null
var startReq: KeyVerificationStart? = null
val aliceAcceptedLatch = CountDownLatch(1)
val aliceListener = object : SasVerificationService.SasVerificationListener {
val aliceListener = object : VerificationService.VerificationListener {
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
override fun transactionCreated(tx: SasVerificationTransaction) {}
override fun transactionCreated(tx: VerificationTransaction) {}
override fun transactionUpdated(tx: SasVerificationTransaction) {
if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnAccepted) {
val at = tx as SASVerificationTransaction
accepted = at.accepted
startReq = at.startReq
override fun transactionUpdated(tx: VerificationTransaction) {
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
val at = tx as SASDefaultVerificationTransaction
accepted = at.accepted as? KeyVerificationAccept
startReq = at.startReq as? KeyVerificationStart
aliceAcceptedLatch.countDown()
}
}
}
aliceSasMgr.addListener(aliceListener)
aliceVerificationService.addListener(aliceListener)
val bobListener = object : SasVerificationService.SasVerificationListener {
override fun transactionCreated(tx: SasVerificationTransaction) {}
val bobListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
override fun transactionUpdated(tx: SasVerificationTransaction) {
if ((tx as IncomingSASVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
val at = tx as IncomingSASVerificationTransaction
override fun transactionUpdated(tx: VerificationTransaction) {
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
val at = tx as IncomingSasVerificationTransaction
at.performAccept()
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
bobSasMgr.addListener(bobListener)
bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.getMyDevice().deviceId
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
mTestHelper.await(aliceAcceptedLatch)
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
@@ -393,38 +429,38 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceSasMgr = aliceSession.getSasVerificationService()
val bobSasMgr = bobSession!!.getSasVerificationService()
val aliceVerificationService = aliceSession.getVerificationService()
val bobVerificationService = bobSession!!.getVerificationService()
val aliceSASLatch = CountDownLatch(1)
val aliceListener = object : SasVerificationService.SasVerificationListener {
override fun transactionCreated(tx: SasVerificationTransaction) {}
val aliceListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
override fun transactionUpdated(tx: SasVerificationTransaction) {
val uxState = (tx as OutgoingSASVerificationRequest).uxState
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
when (uxState) {
OutgoingSasVerificationRequest.UxState.SHOW_SAS -> {
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
aliceSASLatch.countDown()
}
else -> Unit
else -> Unit
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
aliceSasMgr.addListener(aliceListener)
aliceVerificationService.addListener(aliceListener)
val bobSASLatch = CountDownLatch(1)
val bobListener = object : SasVerificationService.SasVerificationListener {
override fun transactionCreated(tx: SasVerificationTransaction) {}
val bobListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
override fun transactionUpdated(tx: SasVerificationTransaction) {
val uxState = (tx as IncomingSASVerificationTransaction).uxState
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as IncomingSasVerificationTransaction).uxState
when (uxState) {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
tx.performAccept()
}
else -> Unit
else -> Unit
}
if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) {
bobSASLatch.countDown()
@@ -433,16 +469,16 @@ class SASTest : InstrumentedTest {
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
bobSasMgr.addListener(bobListener)
bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.getMyDevice().deviceId
val verificationSAS = aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
mTestHelper.await(aliceSASLatch)
mTestHelper.await(bobSASLatch)
val aliceTx = aliceSasMgr.getExistingTransaction(bobUserId, verificationSAS!!) as SASVerificationTransaction
val bobTx = bobSasMgr.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASVerificationTransaction
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))
@@ -457,36 +493,36 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceSasMgr = aliceSession.getSasVerificationService()
val bobSasMgr = bobSession!!.getSasVerificationService()
val aliceVerificationService = aliceSession.getVerificationService()
val bobVerificationService = bobSession!!.getVerificationService()
val aliceSASLatch = CountDownLatch(1)
val aliceListener = object : SasVerificationService.SasVerificationListener {
override fun transactionCreated(tx: SasVerificationTransaction) {}
val aliceListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
override fun transactionUpdated(tx: SasVerificationTransaction) {
val uxState = (tx as OutgoingSASVerificationRequest).uxState
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
when (uxState) {
OutgoingSasVerificationRequest.UxState.SHOW_SAS -> {
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
tx.userHasVerifiedShortCode()
}
OutgoingSasVerificationRequest.UxState.VERIFIED -> {
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
aliceSASLatch.countDown()
}
else -> Unit
else -> Unit
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
aliceSasMgr.addListener(aliceListener)
aliceVerificationService.addListener(aliceListener)
val bobSASLatch = CountDownLatch(1)
val bobListener = object : SasVerificationService.SasVerificationListener {
override fun transactionCreated(tx: SasVerificationTransaction) {}
val bobListener = object : VerificationService.VerificationListener {
override fun transactionCreated(tx: VerificationTransaction) {}
override fun transactionUpdated(tx: SasVerificationTransaction) {
val uxState = (tx as IncomingSASVerificationTransaction).uxState
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as IncomingSasVerificationTransaction).uxState
when (uxState) {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
tx.performAccept()
@@ -497,23 +533,23 @@ class SASTest : InstrumentedTest {
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
bobSASLatch.countDown()
}
else -> Unit
else -> Unit
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
}
bobSasMgr.addListener(bobListener)
bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.getMyDevice().deviceId
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
mTestHelper.await(aliceSASLatch)
mTestHelper.await(bobSASLatch)
// Assert that devices are verified
val bobDeviceInfoFromAlicePOV: MXDeviceInfo? = aliceSession.getDeviceInfo(bobUserId, bobDeviceId)
val aliceDeviceInfoFromBobPOV: MXDeviceInfo? = bobSession.getDeviceInfo(aliceSession.myUserId, aliceSession.getMyDevice().deviceId)
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.getDeviceInfo(bobUserId, bobDeviceId)
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = bobSession.getDeviceInfo(aliceSession.myUserId, aliceSession.getMyDevice().deviceId)
// latch wait a bit again
Thread.sleep(1000)

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4
import im.vector.matrix.android.InstrumentedTest
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
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class SharedSecretTest : InstrumentedTest {
@Test
fun testSharedSecretLengthCase() {
repeat(100) {
generateSharedSecret().length shouldBe 43
}
}
@Test
fun testSharedDiffCase() {
val sharedSecret1 = generateSharedSecret()
val sharedSecret2 = generateSharedSecret()
sharedSecret1 shouldNotBeEqualTo sharedSecret2
}
}

View File

@@ -6,9 +6,10 @@
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application>
<application android:networkSecurityConfig="@xml/network_security_config">
<provider android:name="androidx.work.impl.WorkManagerInitializer"
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
android:exported="false"
tools:node="remove" />

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.crypto
/**
* RoomEncryptionTrustLevel represents the trust level in an encrypted room.
*/
enum class RoomEncryptionTrustLevel {
// No one in the room has been verified -> Black shield
Default,
// There are one or more device un-verified -> the app should display a red shield
Warning,
// All devices in the room are verified -> the app should display a green shield
Trusted
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.extensions
fun Boolean?.orTrue() = this ?: true
fun Boolean?.orFalse() = this ?: false

View File

@@ -17,14 +17,14 @@
package im.vector.matrix.android.api.extensions
import im.vector.matrix.android.api.comparators.DatedObjectComparators
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
/* ==========================================================================================
* MXDeviceInfo
* ========================================================================================== */
fun MXDeviceInfo.getFingerprintHumanReadable() = fingerprint()
fun CryptoDeviceInfo.getFingerprintHumanReadable() = fingerprint()
?.chunked(4)
?.joinToString(separator = " ")

View File

@@ -26,3 +26,8 @@ fun Throwable.is401() =
fun Throwable.isTokenError() =
this is Failure.ServerError
&& (error.code == MatrixError.M_UNKNOWN_TOKEN || error.code == MatrixError.M_MISSING_TOKEN)
fun Throwable.shouldBeRetried(): Boolean {
return this is Failure.NetworkConnection
|| (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED)
}

View File

@@ -19,7 +19,7 @@ package im.vector.matrix.android.api.permalinks
import im.vector.matrix.android.api.session.events.model.Event
/**
* Useful methods to create Matrix permalink.
* Useful methods to create Matrix permalink (matrix.to links).
*/
object PermalinkFactory {
@@ -84,7 +84,17 @@ object PermalinkFactory {
* @param id the id to escape
* @return the escaped id
*/
private fun escape(id: String): String {
internal fun escape(id: String): String {
return id.replace("/", "%2F")
}
/**
* Unescape '/' in id
*
* @param id the id to escape
* @return the escaped id
*/
internal fun unescape(id: String): String {
return id.replace("%2F", "/")
}
}

View File

@@ -17,15 +17,19 @@
package im.vector.matrix.android.api.session.crypto
import android.content.Context
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.listeners.ProgressListener
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService
import im.vector.matrix.android.api.session.crypto.sas.VerificationService
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
@@ -46,7 +50,9 @@ interface CryptoService {
fun isCryptoEnabled(): Boolean
fun getSasVerificationService(): SasVerificationService
fun getVerificationService(): VerificationService
fun getCrossSigningService(): CrossSigningService
fun getKeysBackupService(): KeysBackupService
@@ -54,15 +60,15 @@ interface CryptoService {
fun setWarnOnUnknownDevices(warn: Boolean)
fun setDeviceVerification(verificationStatus: Int, deviceId: String, userId: String)
fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String)
fun getUserDevices(userId: String): MutableList<MXDeviceInfo>
fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo>
fun setDevicesKnown(devices: List<MXDeviceInfo>, callback: MatrixCallback<Unit>?)
fun deviceWithIdentityKey(senderKey: String, algorithm: String): MXDeviceInfo?
fun deviceWithIdentityKey(senderKey: String, algorithm: String): CryptoDeviceInfo?
fun getMyDevice(): MXDeviceInfo
fun getMyDevice(): CryptoDeviceInfo
fun getGlobalBlacklistUnverifiedDevices(): Boolean
@@ -78,7 +84,7 @@ interface CryptoService {
fun setRoomBlacklistUnverifiedDevices(roomId: String)
fun getDeviceInfo(userId: String, deviceId: String?): MXDeviceInfo?
fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
fun reRequestRoomKeyForEvent(event: Event)
@@ -110,7 +116,15 @@ interface CryptoService {
fun shouldEncryptForInvitedMembers(roomId: String): Boolean
fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>)
fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>>)
fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>>
fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>>
fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>>
fun addNewSessionListener(newSessionListener: NewSessionListener)

View File

@@ -18,7 +18,7 @@
package im.vector.matrix.android.api.session.crypto
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import org.matrix.olm.OlmException
@@ -36,7 +36,7 @@ sealed class MXCryptoError : Throwable() {
data class OlmError(val olmException: OlmException) : MXCryptoError()
data class UnknownDevice(val deviceList: MXUsersDevicesMap<MXDeviceInfo>) : MXCryptoError()
data class UnknownDevice(val deviceList: MXUsersDevicesMap<CryptoDeviceInfo>) : MXCryptoError()
enum class ErrorType {
ENCRYPTING_NOT_ENABLED,

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.crypto.crosssigning
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult
import im.vector.matrix.android.internal.crypto.crosssigning.UserTrustResult
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
interface CrossSigningService {
fun isCrossSigningVerified(): Boolean
fun isUserTrusted(otherUserId: String): Boolean
/**
* Will not force a download of the key, but will verify signatures trust chain.
* Checks that my trusted user key has signed the other user UserKey
*/
fun checkUserTrust(otherUserId: String): UserTrustResult
/**
* Initialize cross signing for this user.
* Users needs to enter credentials
*/
fun initializeCrossSigning(authParams: UserPasswordAuth?,
callback: MatrixCallback<Unit>? = null)
fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>>
fun getMyCrossSigningKeys(): MXCrossSigningInfo?
fun canCrossSign(): Boolean
fun trustUser(otherUserId: String,
callback: MatrixCallback<Unit>)
/**
* Sign one of your devices and upload the signature
*/
fun signDevice(deviceId: String,
callback: MatrixCallback<Unit>)
fun checkDeviceTrust(otherUserId: String,
otherDeviceId: String,
locallyTrusted: Boolean?): DeviceTrustResult
fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.crypto.crosssigning
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
import im.vector.matrix.android.internal.crypto.model.KeyUsage
data class MXCrossSigningInfo(
val userId: String,
val crossSigningKeys: List<CryptoCrossSigningKey>
) {
fun isTrusted(): Boolean = masterKey()?.trustLevel?.isVerified() == true
&& selfSigningKey()?.trustLevel?.isVerified() == true
fun masterKey(): CryptoCrossSigningKey? = crossSigningKeys
.firstOrNull { it.usages?.contains(KeyUsage.MASTER.value) == true }
fun userKey(): CryptoCrossSigningKey? = crossSigningKeys
.firstOrNull { it.usages?.contains(KeyUsage.USER_SIGNING.value) == true }
fun selfSigningKey(): CryptoCrossSigningKey? = crossSigningKeys
.firstOrNull { it.usages?.contains(KeyUsage.SELF_SIGNING.value) == true }
}

View File

@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// TODO Rename package
package im.vector.matrix.android.api.session.crypto.sas
enum class CancelCode(val value: String, val humanReadable: String) {
@@ -25,7 +27,9 @@ enum class CancelCode(val value: String, val humanReadable: String) {
UnexpectedMessage("m.unexpected_message", "the device received an unexpected message"),
InvalidMessage("m.invalid_message", "an invalid message was received"),
MismatchedKeys("m.key_mismatch", "Key mismatch"),
UserMismatchError("m.user_error", "User mismatch")
UserError("m.user_error", "User error"),
MismatchedUser("m.user_mismatch", "User mismatch"),
QrCodeInvalid("m.qr_code.invalid", "Invalid QR code")
}
fun safeValueOf(code: String?): CancelCode {

View File

@@ -16,7 +16,7 @@
package im.vector.matrix.android.api.session.crypto.sas
interface IncomingSasVerificationTransaction {
interface IncomingSasVerificationTransaction : SasVerificationTransaction {
val uxState: UxState
fun performAccept()

View File

@@ -16,7 +16,7 @@
package im.vector.matrix.android.api.session.crypto.sas
interface OutgoingSasVerificationRequest {
interface OutgoingSasVerificationTransaction : SasVerificationTransaction {
val uxState: UxState
enum class UxState {

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.crypto.sas
interface QrCodeVerificationTransaction : VerificationTransaction {
/**
* To use to display a qr code, for the other user to scan it
*/
val qrCodeText: String?
/**
* Call when you have scan the other user QR code
*/
fun userHasScannedOtherQrCode(otherQrCodeText: String)
/**
* Call when you confirm that other user has scanned your QR code
*/
fun otherUserScannedMyQrCode()
/**
* Call when you do not confirm that other user has scanned your QR code
*/
fun otherUserDidNotScannedMyQrCode()
}

View File

@@ -1,39 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.crypto.sas
interface SasVerificationService {
fun addListener(listener: SasVerificationListener)
fun removeListener(listener: SasVerificationListener)
fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
fun getExistingTransaction(otherUser: String, tid: String): SasVerificationTransaction?
fun beginKeyVerificationSAS(userId: String, deviceID: String): String?
fun beginKeyVerification(method: String, userId: String, deviceID: String): String?
// fun transactionUpdated(tx: SasVerificationTransaction)
interface SasVerificationListener {
fun transactionCreated(tx: SasVerificationTransaction)
fun transactionUpdated(tx: SasVerificationTransaction)
fun markedAsManuallyVerified(userId: String, deviceId: String)
}
}

View File

@@ -16,18 +16,7 @@
package im.vector.matrix.android.api.session.crypto.sas
interface SasVerificationTransaction {
val state: SasVerificationTxState
val cancelledReason: CancelCode?
val transactionId: String
val otherUserId: String
var otherDeviceId: String?
val isIncoming: Boolean
interface SasVerificationTransaction : VerificationTransaction {
fun supportsEmoji(): Boolean
@@ -37,14 +26,11 @@ interface SasVerificationTransaction {
fun getDecimalCodeRepresentation(): String
/**
* User wants to cancel the transaction
*/
fun cancel()
/**
* To be called by the client when the user has verified that
* both short codes do match
*/
fun userHasVerifiedShortCode()
fun shortCodeDoesNotMatch()
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.crypto.sas
enum class SasVerificationTxState {
None,
// I have started a verification request
SendingStart,
Started,
// Other user/device sent me a request
OnStarted,
// I have accepted a request started by the other user/device
SendingAccept,
Accepted,
// My request has been accepted by the other user/device
OnAccepted,
// I have sent my public key
SendingKey,
KeySent,
// The other user/device has sent me his public key
OnKeyReceived,
// Short code is ready to be displayed
ShortCodeReady,
// I have compared the code and manually said that they match
ShortCodeAccepted,
SendingMac,
MacSent,
Verifying,
Verified,
// Global: The verification has been cancelled (by me or other), see cancelReason for details
Cancelled,
OnCancelled
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.crypto.sas
/**
* Verification methods
*/
enum class VerificationMethod {
// Use it when your application supports the SAS verification method
SAS,
// Use it if your application is able to display QR codes
QR_CODE_SHOW,
// Use it if your application is able to scan QR codes
QR_CODE_SCAN
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.crypto.sas
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.events.model.LocalEcho
import im.vector.matrix.android.internal.crypto.verification.PendingVerificationRequest
/**
* https://matrix.org/docs/spec/client_server/r0.5.0#key-verification-framework
*
* Verifying keys manually by reading out the Ed25519 key is not very user friendly, and can lead to errors.
* Verification is a user-friendly key verification process.
* Verification is intended to be a highly interactive process for users,
* and as such exposes verification methods which are easier for users to use.
*/
interface VerificationService {
fun addListener(listener: VerificationListener)
fun removeListener(listener: VerificationListener)
/**
* Mark this device as verified manually
*/
fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction?
fun getExistingVerificationRequest(otherUserId: String): List<PendingVerificationRequest>?
fun getExistingVerificationRequest(otherUserId: String, tid: String?): PendingVerificationRequest?
fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest?
fun beginKeyVerification(method: VerificationMethod,
otherUserId: String,
otherDeviceId: String,
transactionId: String?): String?
/**
* Request a key verification from another user using toDevice events.
*/
fun requestKeyVerificationInDMs(methods: List<VerificationMethod>,
otherUserId: String,
roomId: String,
localId: String? = LocalEcho.createLocalEchoId()): PendingVerificationRequest
/**
* Request a key verification from another user using toDevice events.
*/
fun requestKeyVerification(methods: List<VerificationMethod>,
otherUserId: String,
otherDevices: List<String>?): PendingVerificationRequest
fun declineVerificationRequestInDMs(otherUserId: String,
otherDeviceId: String,
transactionId: String,
roomId: String)
// Only SAS method is supported for the moment
fun beginKeyVerificationInDMs(method: VerificationMethod,
transactionId: String,
roomId: String,
otherUserId: String,
otherDeviceId: String,
callback: MatrixCallback<String>?): String?
/**
* Returns false if the request is unknown
*/
fun readyPendingVerificationInDMs(methods: List<VerificationMethod>,
otherUserId: String,
roomId: String,
transactionId: String): Boolean
/**
* Returns false if the request is unknown
*/
fun readyPendingVerification(methods: List<VerificationMethod>,
otherUserId: String,
transactionId: String): Boolean
// fun transactionUpdated(tx: SasVerificationTransaction)
interface VerificationListener {
fun transactionCreated(tx: VerificationTransaction)
fun transactionUpdated(tx: VerificationTransaction)
fun markedAsManuallyVerified(userId: String, deviceId: String) {}
fun verificationRequestCreated(pr: PendingVerificationRequest) {}
fun verificationRequestUpdated(pr: PendingVerificationRequest) {}
}
companion object {
private const val TEN_MINUTES_IN_MILLIS = 10 * 60 * 1000
private const val FIVE_MINUTES_IN_MILLIS = 5 * 60 * 1000
fun isValidRequest(age: Long?): Boolean {
if (age == null) return false
val now = System.currentTimeMillis()
val tooInThePast = now - TEN_MINUTES_IN_MILLIS
val tooInTheFuture = now + FIVE_MINUTES_IN_MILLIS
return age in tooInThePast..tooInTheFuture
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.crypto.sas
interface VerificationTransaction {
var state: VerificationTxState
val transactionId: String
val otherUserId: String
var otherDeviceId: String?
// TODO Not used. Remove?
val isIncoming: Boolean
/**
* User wants to cancel the transaction
*/
fun cancel()
fun cancel(code: CancelCode)
fun isToDeviceTransport(): Boolean
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.crypto.sas
sealed class VerificationTxState {
// Uninitialized state
object None : VerificationTxState()
// Specific for SAS
abstract class VerificationSasTxState : VerificationTxState()
object SendingStart : VerificationSasTxState()
object Started : VerificationSasTxState()
object OnStarted : VerificationSasTxState()
object SendingAccept : VerificationSasTxState()
object Accepted : VerificationSasTxState()
object OnAccepted : VerificationSasTxState()
object SendingKey : VerificationSasTxState()
object KeySent : VerificationSasTxState()
object OnKeyReceived : VerificationSasTxState()
object ShortCodeReady : VerificationSasTxState()
object ShortCodeAccepted : VerificationSasTxState()
object SendingMac : VerificationSasTxState()
object MacSent : VerificationSasTxState()
object Verifying : VerificationSasTxState()
// Specific for QR code
abstract class VerificationQrTxState : VerificationTxState()
// Will be used to ask the user if the other user has correctly scanned
object QrScannedByOther : VerificationQrTxState()
// Terminal states
abstract class TerminalTxState : VerificationTxState()
object Verified : TerminalTxState()
// Cancelled by me or by other
data class Cancelled(val cancelCode: CancelCode, val byMe: Boolean) : TerminalTxState()
}

View File

@@ -85,6 +85,14 @@ data class Event(
@Transient
var sendState: SendState = SendState.UNKNOWN
/**
* The `age` value transcoded in a timestamp based on the device clock when the SDK received
* the event from the home server.
* Unlike `age`, this value is static.
*/
@Transient
var ageLocalTs: Long? = null
/**
* Check if event is a state event.
* @return true if event is state event.

View File

@@ -72,6 +72,8 @@ object EventType {
const val KEY_VERIFICATION_KEY = "m.key.verification.key"
const val KEY_VERIFICATION_MAC = "m.key.verification.mac"
const val KEY_VERIFICATION_CANCEL = "m.key.verification.cancel"
const val KEY_VERIFICATION_DONE = "m.key.verification.done"
const val KEY_VERIFICATION_READY = "m.key.verification.ready"
// Relation Events
const val REACTION = "m.reaction"

View File

@@ -21,9 +21,23 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class UnsignedData(
/**
* The time in milliseconds that has elapsed since the event was sent.
* This field is generated by the local homeserver, and may be incorrect if the local time on at least one of the two servers
* is out of sync, which can cause the age to either be negative or greater than it actually is.
*/
@Json(name = "age") val age: Long?,
/**
* Optional. The event that redacted this event, if any.
*/
@Json(name = "redacted_because") val redactedEvent: Event? = null,
/**
* The client-supplied transaction ID, if the client being given the event is the same one which sent it.
*/
@Json(name = "transaction_id") val transactionId: String? = null,
/**
* Optional. The previous content for this event. If there is no previous content, this key will be missing.
*/
@Json(name = "prev_content") val prevContent: Map<String, Any>? = null,
@Json(name = "m.relations") val relations: AggregatedRelations? = null
)

View File

@@ -101,4 +101,6 @@ interface RoomService {
fun getRoomIdByAlias(roomAlias: String,
searchOnServer: Boolean,
callback: MatrixCallback<Optional<String>>): Cancelable
fun getExistingDirectRoomWithUser(otherUserId: String) : Room?
}

View File

@@ -18,5 +18,6 @@ package im.vector.matrix.android.api.session.room.model
data class EventAnnotationsSummary(
var eventId: String,
var reactionsSummary: List<ReactionAggregatedSummary>,
var editSummary: EditAggregatedSummary?
var editSummary: EditAggregatedSummary?,
var referencesAggregatedSummary: ReferencesAggregatedSummary? = null
)

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Contains an aggregated summary info of the references.
* Put pre-computed info that you want to access quickly without having
* to go through all references events
*/
@JsonClass(generateAdapter = true)
data class ReferencesAggregatedContent(
// Verification status info for m.key.verification.request msgType events
@Json(name = "verif_sum") val verificationSummary: String
// Add more fields for future summary info.
)

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model
import im.vector.matrix.android.api.session.events.model.Content
/**
* Events can relates to other events, this object keeps a summary
* of all events that are referencing the 'eventId' event via the RelationType.REFERENCE
*/
data class ReferencesAggregatedSummary(
val eventId: String,
val content: Content?,
val sourceEvents: List<String>,
val localEchos: List<String>
)

View File

@@ -16,12 +16,16 @@
package im.vector.matrix.android.api.session.room.model
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
/**
* Class representing a simplified version of EventType.STATE_ROOM_MEMBER state event content
*/
data class RoomMemberSummary(
data class RoomMemberSummary constructor(
val membership: Membership,
val userId: String,
val displayName: String? = null,
val avatarUrl: String? = null
val avatarUrl: String? = null,
// TODO Warning: Will not be populated if not using RxRoom
val userEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
)

View File

@@ -16,6 +16,7 @@
package im.vector.matrix.android.api.session.room.model
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
@@ -24,7 +25,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
* This class holds some data of a room.
* It can be retrieved by [im.vector.matrix.android.api.session.room.Room] and [im.vector.matrix.android.api.session.room.RoomService]
*/
data class RoomSummary(
data class RoomSummary constructor(
val roomId: String,
val displayName: String = "",
val topic: String = "",
@@ -45,7 +46,10 @@ data class RoomSummary(
val readMarkerId: String? = null,
val userDrafts: List<UserDraft> = emptyList(),
var isEncrypted: Boolean,
val typingRoomMemberIds: List<String> = emptyList()
val typingRoomMemberIds: List<String> = emptyList(),
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
// TODO Plug it
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null
) {
val isVersioned: Boolean
@@ -53,4 +57,8 @@ data class RoomSummary(
val hasNewMessages: Boolean
get() = notificationCount != 0
companion object {
const val NOT_IN_BREADCRUMBS = -1
}
}

View File

@@ -35,95 +35,111 @@ import timber.log.Timber
* Parameter to create a room, with facilities functions to configure it
*/
@JsonClass(generateAdapter = true)
class CreateRoomParams {
data class CreateRoomParams(
/**
* A public visibility indicates that the room will be shown in the published room list.
* A private visibility will hide the room from the published room list.
* Rooms default to private visibility if this key is not included.
* NB: This should not be confused with join_rules which also uses the word public. One of: ["public", "private"]
*/
@Json(name = "visibility")
val visibility: RoomDirectoryVisibility? = null,
/**
* A public visibility indicates that the room will be shown in the published room list.
* A private visibility will hide the room from the published room list.
* Rooms default to private visibility if this key is not included.
* NB: This should not be confused with join_rules which also uses the word public. One of: ["public", "private"]
*/
var visibility: RoomDirectoryVisibility? = null
/**
* The desired room alias local part. If this is included, a room alias will be created and mapped to the newly created room.
* The alias will belong on the same homeserver which created the room.
* For example, if this was set to "foo" and sent to the homeserver "example.com" the complete room alias would be #foo:example.com.
*/
@Json(name = "room_alias_name")
val roomAliasName: String? = null,
/**
* The desired room alias local part. If this is included, a room alias will be created and mapped to the newly created room.
* The alias will belong on the same homeserver which created the room.
* For example, if this was set to "foo" and sent to the homeserver "example.com" the complete room alias would be #foo:example.com.
*/
@Json(name = "room_alias_name")
var roomAliasName: String? = null
/**
* If this is included, an m.room.name event will be sent into the room to indicate the name of the room.
* See Room Events for more information on m.room.name.
*/
@Json(name = "name")
val name: String? = null,
/**
* If this is included, an m.room.name event will be sent into the room to indicate the name of the room.
* See Room Events for more information on m.room.name.
*/
var name: String? = null
/**
* If this is included, an m.room.topic event will be sent into the room to indicate the topic for the room.
* See Room Events for more information on m.room.topic.
*/
@Json(name = "topic")
val topic: String? = null,
/**
* If this is included, an m.room.topic event will be sent into the room to indicate the topic for the room.
* See Room Events for more information on m.room.topic.
*/
var topic: String? = null
/**
* A list of user IDs to invite to the room.
* This will tell the server to invite everyone in the list to the newly created room.
*/
@Json(name = "invite")
val invitedUserIds: List<String>? = null,
/**
* A list of user IDs to invite to the room.
* This will tell the server to invite everyone in the list to the newly created room.
*/
@Json(name = "invite")
var invitedUserIds: MutableList<String>? = null
/**
* A list of objects representing third party IDs to invite into the room.
*/
@Json(name = "invite_3pid")
val invite3pids: List<Invite3Pid>? = null,
/**
* A list of objects representing third party IDs to invite into the room.
*/
@Json(name = "invite_3pid")
var invite3pids: MutableList<Invite3Pid>? = null
/**
* Extra keys to be added to the content of the m.room.create.
* The server will clobber the following keys: creator.
* Future versions of the specification may allow the server to clobber other keys.
*/
@Json(name = "creation_content")
val creationContent: Any? = null,
/**
* Extra keys to be added to the content of the m.room.create.
* The server will clobber the following keys: creator.
* Future versions of the specification may allow the server to clobber other keys.
*/
@Json(name = "creation_content")
var creationContent: Any? = null
/**
* A list of state events to set in the new room.
* This allows the user to override the default state events set in the new room.
* The expected format of the state events are an object with type, state_key and content keys set.
* Takes precedence over events set by presets, but gets overridden by name and topic keys.
*/
@Json(name = "initial_state")
val initialStates: List<Event>? = null,
/**
* A list of state events to set in the new room.
* This allows the user to override the default state events set in the new room.
* The expected format of the state events are an object with type, state_key and content keys set.
* Takes precedence over events set by presets, but gets overridden by name and topic keys.
*/
@Json(name = "initial_state")
var initialStates: MutableList<Event>? = null
/**
* Convenience parameter for setting various default state events based on a preset. Must be either:
* private_chat => join_rules is set to invite. history_visibility is set to shared.
* trusted_private_chat => join_rules is set to invite. history_visibility is set to shared. All invitees are given the same power level as the
* room creator.
* public_chat: => join_rules is set to public. history_visibility is set to shared.
*/
@Json(name = "preset")
val preset: CreateRoomPreset? = null,
/**
* Convenience parameter for setting various default state events based on a preset. Must be either:
* private_chat => join_rules is set to invite. history_visibility is set to shared.
* trusted_private_chat => join_rules is set to invite. history_visibility is set to shared. All invitees are given the same power level as the
* room creator.
* public_chat: => join_rules is set to public. history_visibility is set to shared.
*/
var preset: CreateRoomPreset? = null
/**
* This flag makes the server set the is_direct flag on the m.room.member events sent to the users in invite and invite_3pid.
* See Direct Messaging for more information.
*/
@Json(name = "is_direct")
val isDirect: Boolean? = null,
/**
* The power level content to override in the default power level event
*/
@Json(name = "power_level_content_override")
val powerLevelContentOverride: PowerLevelsContent? = null
) {
/**
* This flag makes the server set the is_direct flag on the m.room.member events sent to the users in invite and invite_3pid.
* See Direct Messaging for more information.
* Set to true means that if cross-signing is enabled and we can get keys for every invited users,
* the encryption will be enabled on the created room
*/
@Json(name = "is_direct")
var isDirect: Boolean? = null
@Transient
internal var enableEncryptionIfInvitedUsersSupportIt: Boolean = false
private set
/**
* The power level content to override in the default power level event
*/
@Json(name = "power_level_content_override")
var powerLevelContentOverride: PowerLevelsContent? = null
fun enableEncryptionIfInvitedUsersSupportIt(): CreateRoomParams {
enableEncryptionIfInvitedUsersSupportIt = true
return this
}
/**
* Add the crypto algorithm to the room creation parameters.
*
* @param algorithm the algorithm
*/
fun enableEncryptionWithAlgorithm(algorithm: String) {
if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
fun enableEncryptionWithAlgorithm(algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM): CreateRoomParams {
return if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
val contentMap = mapOf("algorithm" to algorithm)
val algoEvent = Event(
@@ -132,13 +148,12 @@ class CreateRoomParams {
content = contentMap.toContent()
)
if (null == initialStates) {
initialStates = mutableListOf(algoEvent)
} else {
initialStates!!.add(algoEvent)
}
copy(
initialStates = initialStates.orEmpty().filter { it.type != EventType.STATE_ROOM_ENCRYPTION } + algoEvent
)
} else {
Timber.e("Unsupported algorithm: $algorithm")
this
}
}
@@ -147,9 +162,10 @@ class CreateRoomParams {
*
* @param historyVisibility the expected history visibility, set null to remove any existing value.
*/
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?) {
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?): CreateRoomParams {
// Remove the existing value if any.
initialStates?.removeAll { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY }
val newInitialStates = initialStates
?.filter { it.type != EventType.STATE_ROOM_HISTORY_VISIBILITY }
if (historyVisibility != null) {
val contentMap = mapOf("history_visibility" to historyVisibility)
@@ -159,20 +175,24 @@ class CreateRoomParams {
stateKey = "",
content = contentMap.toContent())
if (null == initialStates) {
initialStates = mutableListOf(historyVisibilityEvent)
} else {
initialStates!!.add(historyVisibilityEvent)
}
return copy(
initialStates = newInitialStates.orEmpty() + historyVisibilityEvent
)
} else {
return copy(
initialStates = newInitialStates
)
}
}
/**
* Mark as a direct message room.
*/
fun setDirectMessage() {
preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT
isDirect = true
fun setDirectMessage(): CreateRoomParams {
return copy(
preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT,
isDirect = true
)
}
/**
@@ -215,28 +235,26 @@ class CreateRoomParams {
*/
fun addParticipantIds(hsConfig: HomeServerConnectionConfig,
userId: String,
ids: List<String>) {
for (id in ids) {
if (Patterns.EMAIL_ADDRESS.matcher(id).matches() && hsConfig.identityServerUri != null) {
if (null == invite3pids) {
invite3pids = ArrayList()
}
val pid = Invite3Pid(idServer = hsConfig.identityServerUri.host!!,
medium = ThreePidMedium.EMAIL,
address = id)
invite3pids!!.add(pid)
} else if (isUserId(id)) {
// do not invite oneself
if (userId != id) {
if (null == invitedUserIds) {
invitedUserIds = ArrayList()
}
invitedUserIds!!.add(id)
}
}
// TODO add phonenumbers when it will be available
}
ids: List<String>): CreateRoomParams {
return copy(
invite3pids = (invite3pids.orEmpty() + ids
.takeIf { hsConfig.identityServerUri != null }
?.filter { id -> Patterns.EMAIL_ADDRESS.matcher(id).matches() }
?.map { id ->
Invite3Pid(
idServer = hsConfig.identityServerUri!!.host!!,
medium = ThreePidMedium.EMAIL,
address = id
)
}
.orEmpty())
.distinct(),
invitedUserIds = (invitedUserIds.orEmpty() + ids
.filter { id -> isUserId(id) }
// do not invite oneself
.filter { id -> id != userId })
.distinct()
)
// TODO add phonenumbers when it will be available
}
}

View File

@@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
interface MessageContent {
// TODO Rename to msgType
val type: String
val body: String
val relatesTo: RelationDefaultContent?

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
@JsonClass(generateAdapter = true)
data class MessageRelationContent(
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
)

View File

@@ -26,6 +26,7 @@ object MessageType {
const val MSGTYPE_VIDEO = "m.video"
const val MSGTYPE_LOCATION = "m.location"
const val MSGTYPE_FILE = "m.file"
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
const val FORMAT_MATRIX_HTML = "org.matrix.custom.html"
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
// Because sticker isn't a message type but a event type without msgtype field

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAccept
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAcceptFactory
import timber.log.Timber
@JsonClass(generateAdapter = true)
internal data class MessageVerificationAcceptContent(
@Json(name = "hash") override val hash: String?,
@Json(name = "key_agreement_protocol") override val keyAgreementProtocol: String?,
@Json(name = "message_authentication_code") override val messageAuthenticationCode: String?,
@Json(name = "short_authentication_string") override val shortAuthenticationStrings: List<String>?,
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?,
@Json(name = "commitment") override var commitment: String? = null
) : VerificationInfoAccept {
override val transactionID: String?
get() = relatesTo?.eventId
override fun isValid(): Boolean {
if (transactionID.isNullOrBlank()
|| keyAgreementProtocol.isNullOrBlank()
|| hash.isNullOrBlank()
|| commitment.isNullOrBlank()
|| messageAuthenticationCode.isNullOrBlank()
|| shortAuthenticationStrings.isNullOrEmpty()) {
Timber.e("## received invalid verification request")
return false
}
return true
}
override fun toEventContent() = toContent()
companion object : VerificationInfoAcceptFactory {
override fun create(tid: String,
keyAgreementProtocol: String,
hash: String,
commitment: String,
messageAuthenticationCode: String,
shortAuthenticationStrings: List<String>): VerificationInfoAccept {
return MessageVerificationAcceptContent(
hash,
keyAgreementProtocol,
messageAuthenticationCode,
shortAuthenticationStrings,
RelationDefaultContent(
RelationType.REFERENCE,
tid
),
commitment
)
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoCancel
@JsonClass(generateAdapter = true)
data class MessageVerificationCancelContent(
@Json(name = "code") override val code: String? = null,
@Json(name = "reason") override val reason: String? = null,
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
) : VerificationInfoCancel {
override val transactionID: String?
get() = relatesTo?.eventId
override fun toEventContent() = toContent()
override fun isValid(): Boolean {
if (transactionID.isNullOrBlank() || code.isNullOrBlank()) {
return false
}
return true
}
companion object {
fun create(transactionId: String, reason: CancelCode): MessageVerificationCancelContent {
return MessageVerificationCancelContent(
reason.value,
reason.humanReadable,
RelationDefaultContent(
RelationType.REFERENCE,
transactionId
)
)
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
import im.vector.matrix.android.internal.crypto.verification.VerificationInfo
@JsonClass(generateAdapter = true)
internal data class MessageVerificationDoneContent(
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
) : VerificationInfo {
override val transactionID: String?
get() = relatesTo?.eventId
override fun isValid() = transactionID?.isNotEmpty() == true
override fun toEventContent(): Content? = toContent()
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKey
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKeyFactory
import timber.log.Timber
@JsonClass(generateAdapter = true)
internal data class MessageVerificationKeyContent(
/**
* The devices ephemeral public key, as an unpadded base64 string
*/
@Json(name = "key") override val key: String? = null,
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
) : VerificationInfoKey {
override val transactionID: String?
get() = relatesTo?.eventId
override fun isValid(): Boolean {
if (transactionID.isNullOrBlank() || key.isNullOrBlank()) {
Timber.e("## received invalid verification request")
return false
}
return true
}
override fun toEventContent() = toContent()
companion object : VerificationInfoKeyFactory {
override fun create(tid: String, pubKey: String): VerificationInfoKey {
return MessageVerificationKeyContent(
pubKey,
RelationDefaultContent(
RelationType.REFERENCE,
tid
)
)
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoMac
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoMacFactory
@JsonClass(generateAdapter = true)
internal data class MessageVerificationMacContent(
@Json(name = "mac") override val mac: Map<String, String>? = null,
@Json(name = "keys") override val keys: String? = null,
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
) : VerificationInfoMac {
override val transactionID: String?
get() = relatesTo?.eventId
override fun toEventContent() = toContent()
override fun isValid(): Boolean {
if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) {
return false
}
return true
}
companion object : VerificationInfoMacFactory {
override fun create(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac {
return MessageVerificationMacContent(
mac,
keys,
RelationDefaultContent(
RelationType.REFERENCE,
tid
)
)
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
import im.vector.matrix.android.internal.crypto.verification.MessageVerificationReadyFactory
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoReady
@JsonClass(generateAdapter = true)
internal data class MessageVerificationReadyContent(
@Json(name = "from_device") override val fromDevice: String? = null,
@Json(name = "methods") override val methods: List<String>? = null,
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?
) : VerificationInfoReady {
override val transactionID: String?
get() = relatesTo?.eventId
override fun toEventContent() = toContent()
override fun isValid(): Boolean {
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
return false
}
return true
}
companion object : MessageVerificationReadyFactory {
override fun create(tid: String, methods: List<String>, fromDevice: String): VerificationInfoReady {
return MessageVerificationReadyContent(
fromDevice = fromDevice,
methods = methods,
relatesTo = RelationDefaultContent(
RelationType.REFERENCE,
tid
)
)
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoRequest
@JsonClass(generateAdapter = true)
data class MessageVerificationRequestContent(
@Json(name = "msgtype") override val type: String = MessageType.MSGTYPE_VERIFICATION_REQUEST,
@Json(name = "body") override val body: String,
@Json(name = "from_device") override val fromDevice: String?,
@Json(name = "methods") override val methods: List<String>,
@Json(name = "to") val toUserId: String,
@Json(name = "timestamp") override val timestamp: Long?,
@Json(name = "format") val format: String? = null,
@Json(name = "formatted_body") val formattedBody: String? = null,
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@Json(name = "m.new_content") override val newContent: Content? = null
) : MessageContent, VerificationInfoRequest {
override fun isValid(): Boolean {
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
return false
}
return true
}
override val transactionID: String?
get() = relatesTo?.eventId
override fun toEventContent() = toContent()
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.message
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.crypto.sas.SasMode
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.relation.RelationDefaultContent
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
import im.vector.matrix.android.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
import im.vector.matrix.android.internal.crypto.verification.SASDefaultVerificationTransaction
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
import im.vector.matrix.android.internal.util.JsonCanonicalizer
import timber.log.Timber
@JsonClass(generateAdapter = true)
internal data class MessageVerificationStartContent(
@Json(name = "from_device") override val fromDevice: String?,
@Json(name = "hashes") override val hashes: List<String>?,
@Json(name = "key_agreement_protocols") override val keyAgreementProtocols: List<String>?,
@Json(name = "message_authentication_codes") override val messageAuthenticationCodes: List<String>?,
@Json(name = "short_authentication_string") override val shortAuthenticationStrings: List<String>?,
@Json(name = "method") override val method: String?,
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent?,
@Json(name = "secret") override val sharedSecret: String?
) : VerificationInfoStart {
override fun toCanonicalJson(): String? {
return JsonCanonicalizer.getCanonicalJson(MessageVerificationStartContent::class.java, this)
}
override val transactionID: String?
get() = relatesTo?.eventId
// TODO Move those method to the interface?
override fun isValid(): Boolean {
if (transactionID.isNullOrBlank()
|| fromDevice.isNullOrBlank()
|| (method == VERIFICATION_METHOD_SAS && !isValidSas())
|| (method == VERIFICATION_METHOD_RECIPROCATE && !isValidReciprocate())) {
Timber.e("## received invalid verification request")
return false
}
return true
}
private fun isValidSas(): Boolean {
if (keyAgreementProtocols.isNullOrEmpty()
|| hashes.isNullOrEmpty()
|| !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty()
|| (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
&& !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF))
|| shortAuthenticationStrings.isNullOrEmpty()
|| !shortAuthenticationStrings.contains(SasMode.DECIMAL)) {
return false
}
return true
}
private fun isValidReciprocate(): Boolean {
if (sharedSecret.isNullOrBlank()) {
return false
}
return true
}
override fun toEventContent() = toContent()
}

View File

@@ -30,3 +30,7 @@ const val MXCRYPTO_ALGORITHM_MEGOLM = "m.megolm.v1.aes-sha2"
* Matrix algorithm value for megolm keys backup.
*/
const val MXCRYPTO_ALGORITHM_MEGOLM_BACKUP = "m.megolm_backup.v1.curve25519-aes-sha2"
// TODO Refacto: use this constants everywhere
const val ed25519 = "ed25519"
const val curve25519 = "curve25519"

View File

@@ -21,14 +21,68 @@ import dagger.Module
import dagger.Provides
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.*
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultCreateKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteBackupTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultDeleteSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupLastVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultGetSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultStoreSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DefaultUpdateKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteBackupTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.DeleteSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.GetSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessionDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
import im.vector.matrix.android.internal.crypto.tasks.*
import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
import im.vector.matrix.android.internal.crypto.tasks.DefaultDeleteDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultDeleteDeviceWithUserPasswordTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultDownloadKeysForUsers
import im.vector.matrix.android.internal.crypto.tasks.DefaultEncryptEventTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDeviceInfoTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDevicesTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultSendToDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultSendVerificationMessageTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultSetDeviceNameTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultUploadKeysTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultUploadSignaturesTask
import im.vector.matrix.android.internal.crypto.tasks.DefaultUploadSigningKeysTask
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
import im.vector.matrix.android.internal.crypto.tasks.EncryptEventTask
import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.SendVerificationMessageTask
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
import im.vector.matrix.android.internal.crypto.tasks.UploadSignaturesTask
import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask
import im.vector.matrix.android.internal.database.RealmKeysUtils
import im.vector.matrix.android.internal.di.CryptoDatabase
import im.vector.matrix.android.internal.di.SessionFilesDirectory
@@ -132,6 +186,12 @@ internal abstract class CryptoModule {
@Binds
abstract fun bindUploadKeysTask(uploadKeysTask: DefaultUploadKeysTask): UploadKeysTask
@Binds
abstract fun bindUploadSigningKeysTask(uploadKeysTask: DefaultUploadSigningKeysTask): UploadSigningKeysTask
@Binds
abstract fun bindUploadSignaturesTask(uploadSignaturesTask: DefaultUploadSignaturesTask): UploadSignaturesTask
@Binds
abstract fun bindDownloadKeysForUsersTask(downloadKeysForUsersTask: DefaultDownloadKeysForUsers): DownloadKeysForUsersTask
@@ -180,6 +240,12 @@ internal abstract class CryptoModule {
@Binds
abstract fun bindSendToDeviceTask(sendToDeviceTask: DefaultSendToDeviceTask): SendToDeviceTask
@Binds
abstract fun bindEncryptEventTask(encryptEventTask: DefaultEncryptEventTask): EncryptEventTask
@Binds
abstract fun bindSendVerificationMessageTask(sendDefaultSendVerificationMessageTask: DefaultSendVerificationMessageTask): SendVerificationMessageTask
@Binds
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(claimOneTimeKeysForUsersDevice: DefaultClaimOneTimeKeysForUsersDevice)
: ClaimOneTimeKeysForUsersDeviceTask
@@ -187,4 +253,7 @@ internal abstract class CryptoModule {
@Binds
abstract fun bindDeleteDeviceWithUserPasswordTask(deleteDeviceWithUserPasswordTask: DefaultDeleteDeviceWithUserPasswordTask)
: DeleteDeviceWithUserPasswordTask
@Binds
abstract fun bindCrossSigningService(crossSigningService: DefaultCrossSigningService): CrossSigningService
}

View File

@@ -21,6 +21,7 @@ package im.vector.matrix.android.internal.crypto
import android.content.Context
import android.os.Handler
import android.os.Looper
import androidx.lifecycle.LiveData
import com.squareup.moshi.Types
import com.zhuinden.monarchy.Monarchy
import dagger.Lazy
@@ -44,7 +45,10 @@ import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAct
import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting
import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmEncryptionFactory
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXEncryptEventContentResult
@@ -54,11 +58,18 @@ import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody
import im.vector.matrix.android.internal.crypto.model.toRest
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.*
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceTask
import im.vector.matrix.android.internal.crypto.tasks.DeleteDeviceWithUserPasswordTask
import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
import im.vector.matrix.android.internal.crypto.tasks.UploadKeysTask
import im.vector.matrix.android.internal.crypto.verification.DefaultVerificationService
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.extensions.foldToCallback
@@ -71,7 +82,12 @@ import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.JsonCanonicalizer
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.fetchCopied
import kotlinx.coroutines.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.matrix.olm.OlmManager
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
@@ -111,8 +127,10 @@ internal class DefaultCryptoService @Inject constructor(
private val oneTimeKeysUploader: OneTimeKeysUploader,
//
private val roomDecryptorProvider: RoomDecryptorProvider,
// The SAS verification service.
private val sasVerificationService: DefaultSasVerificationService,
// The verification service.
private val verificationService: DefaultVerificationService,
private val crossSigningService: DefaultCrossSigningService,
//
private val incomingRoomKeyRequestManager: IncomingRoomKeyRequestManager,
//
@@ -138,6 +156,10 @@ internal class DefaultCryptoService @Inject constructor(
private val cryptoCoroutineScope: CoroutineScope
) : CryptoService {
init {
verificationService.cryptoService = this
}
private val uiHandler = Handler(Looper.getMainLooper())
// MXEncrypting instance for each room.
@@ -164,7 +186,17 @@ internal class DefaultCryptoService @Inject constructor(
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
setDeviceNameTask
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
this.callback = callback
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
// bg refresh of crypto device
downloadKeys(listOf(credentials.userId), true, object : MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {})
callback.onSuccess(data)
}
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
}
}
.executeBy(taskExecutor)
}
@@ -189,7 +221,7 @@ internal class DefaultCryptoService @Inject constructor(
return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version
}
override fun getMyDevice(): MXDeviceInfo {
override fun getMyDevice(): CryptoDeviceInfo {
return myDeviceInfoHolder.get().myDevice
}
@@ -309,9 +341,11 @@ internal class DefaultCryptoService @Inject constructor(
override fun getKeysBackupService() = keysBackup
/**
* @return the SasVerificationService
* @return the VerificationService
*/
override fun getSasVerificationService() = sasVerificationService
override fun getVerificationService() = verificationService
override fun getCrossSigningService() = crossSigningService
/**
* A sync response has been received
@@ -345,7 +379,7 @@ internal class DefaultCryptoService @Inject constructor(
* @param algorithm the encryption algorithm.
* @return the device info, or null if not found / unsupported algorithm / crypto released
*/
override fun deviceWithIdentityKey(senderKey: String, algorithm: String): MXDeviceInfo? {
override fun deviceWithIdentityKey(senderKey: String, algorithm: String): CryptoDeviceInfo? {
return if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM && algorithm != MXCRYPTO_ALGORITHM_OLM) {
// We only deal in olm keys
null
@@ -358,13 +392,28 @@ internal class DefaultCryptoService @Inject constructor(
* @param userId the user id
* @param deviceId the device id
*/
override fun getDeviceInfo(userId: String, deviceId: String?): MXDeviceInfo? {
override fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
cryptoStore.getUserDevice(deviceId, userId)
cryptoStore.getUserDevice(userId, deviceId)
} else {
null
}
}
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
return cryptoStore.getUserDevices(userId)?.map { it.value } ?: emptyList()
}
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
return cryptoStore.getLiveDeviceList()
}
override fun getLiveCryptoDeviceInfo(userId: String): LiveData<List<CryptoDeviceInfo>> {
return cryptoStore.getLiveDeviceList(userId)
}
override fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> {
return cryptoStore.getLiveDeviceList(userIds)
}
/**
* Set the devices as known
@@ -389,7 +438,7 @@ internal class DefaultCryptoService @Inject constructor(
// assume if the device is either verified or blocked
// it means that the device is known
if (device?.isUnknown == true) {
device.verified = MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED
device.trustLevel = DeviceTrustLevel(crossSigningVerified = false, locallyVerified = false)
isUpdated = true
}
}
@@ -406,12 +455,12 @@ internal class DefaultCryptoService @Inject constructor(
/**
* Update the blocked/verified state of the given device.
*
* @param verificationStatus the new verification status
* @param deviceId the unique identifier for the device.
* @param userId the owner of the device
* @param trustLevel the new trust level
* @param userId the owner of the device
* @param deviceId the unique identifier for the device.
*/
override fun setDeviceVerification(verificationStatus: Int, deviceId: String, userId: String) {
setDeviceVerificationAction.handle(verificationStatus, deviceId, userId)
override fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
setDeviceVerificationAction.handle(trustLevel, userId, deviceId)
}
/**
@@ -475,14 +524,16 @@ internal class DefaultCryptoService @Inject constructor(
}
/**
* Tells if a room is encrypted
* Tells if a room is encrypted with MXCRYPTO_ALGORITHM_MEGOLM
*
* @param roomId the room id
* @return true if the room is encrypted
* @return true if the room is encrypted with algorithm MXCRYPTO_ALGORITHM_MEGOLM
*/
override fun isRoomEncrypted(roomId: String): Boolean {
val encryptionEvent = monarchy.fetchCopied {
EventEntity.where(it, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION).findFirst()
val encryptionEvent = monarchy.fetchCopied { realm ->
EventEntity.where(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
.contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
.findFirst()
}
return encryptionEvent != null
}
@@ -490,9 +541,8 @@ internal class DefaultCryptoService @Inject constructor(
/**
* @return the stored device keys for a user.
*/
override fun getUserDevices(userId: String): MutableList<MXDeviceInfo> {
val map = cryptoStore.getUserDevices(userId)
return if (null != map) ArrayList(map.values) else ArrayList()
override fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
return cryptoStore.getUserDevices(userId)?.values?.toMutableList() ?: ArrayList()
}
fun isEncryptionEnabledForInvitedUser(): Boolean {
@@ -754,11 +804,15 @@ internal class DefaultCryptoService @Inject constructor(
// Prepare the device keys data to send
// Sign it
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
getMyDevice().signatures = objectSigner.signObject(canonicalJson)
var rest = getMyDevice().toRest()
rest = rest.copy(
signatures = objectSigner.signObject(canonicalJson)
)
// For now, we set the device id explicitly, as we may not be using the
// same one as used in login.
val uploadDeviceKeysParams = UploadKeysTask.Params(getMyDevice().toDeviceKeys(), null, getMyDevice().deviceId)
val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null, getMyDevice().deviceId)
return uploadKeysTask.execute(uploadDeviceKeysParams)
}
@@ -998,8 +1052,8 @@ internal class DefaultCryptoService @Inject constructor(
* @param devicesInRoom the devices map
* @return the unknown devices map
*/
private fun getUnknownDevices(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): MXUsersDevicesMap<MXDeviceInfo> {
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
private fun getUnknownDevices(devicesInRoom: MXUsersDevicesMap<CryptoDeviceInfo>): MXUsersDevicesMap<CryptoDeviceInfo> {
val unknownDevices = MXUsersDevicesMap<CryptoDeviceInfo>()
val userIds = devicesInRoom.userIds
for (userId in userIds) {
devicesInRoom.getUserDeviceIds(userId)?.forEach { deviceId ->
@@ -1014,7 +1068,7 @@ internal class DefaultCryptoService @Inject constructor(
return unknownDevices
}
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) {
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>>) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
runCatching {
deviceListManager.downloadKeys(userIds, forceDownload)

View File

@@ -19,7 +19,9 @@ package im.vector.matrix.android.internal.crypto
import im.vector.matrix.android.api.MatrixPatterns
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.CryptoInfoMapper
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
@@ -36,6 +38,36 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
private val credentials: Credentials,
private val downloadKeysForUsersTask: DownloadKeysForUsersTask) {
interface UserDevicesUpdateListener {
fun onUsersDeviceUpdate(users: List<String>)
}
private val deviceChangeListeners = mutableListOf<UserDevicesUpdateListener>()
fun addListener(listener: UserDevicesUpdateListener) {
synchronized(deviceChangeListeners) {
deviceChangeListeners.add(listener)
}
}
fun removeListener(listener: UserDevicesUpdateListener) {
synchronized(deviceChangeListeners) {
deviceChangeListeners.remove(listener)
}
}
private fun dispatchDeviceChange(users: List<String>) {
synchronized(deviceChangeListeners) {
deviceChangeListeners.forEach {
try {
it.onUsersDeviceUpdate(users)
} catch (failure: Throwable) {
Timber.e(failure, "Failed to dispatch device change")
}
}
}
}
// HS not ready for retry
private val notReadyToRetryHS = mutableSetOf<String>()
@@ -166,13 +198,13 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
* @param userIds the userIds list
* @param failures the failure map.
*/
private fun onKeysDownloadSucceed(userIds: List<String>, failures: Map<String, Map<String, Any>>?): MXUsersDevicesMap<MXDeviceInfo> {
private fun onKeysDownloadSucceed(userIds: List<String>, failures: Map<String, Map<String, Any>>?): MXUsersDevicesMap<CryptoDeviceInfo> {
if (failures != null) {
for ((k, value) in failures) {
val statusCode = when (val status = value["status"]) {
is Double -> status.toInt()
is Int -> status.toInt()
else -> 0
is Int -> status.toInt()
else -> 0
}
if (statusCode == 503) {
synchronized(notReadyToRetryHS) {
@@ -182,7 +214,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
}
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
val usersDevicesInfoMap = MXUsersDevicesMap<MXDeviceInfo>()
val usersDevicesInfoMap = MXUsersDevicesMap<CryptoDeviceInfo>()
for (userId in userIds) {
val devices = cryptoStore.getUserDevices(userId)
if (null == devices) {
@@ -207,6 +239,8 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
}
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
dispatchDeviceChange(userIds)
return usersDevicesInfoMap
}
@@ -217,10 +251,10 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
* @param userIds The users to fetch.
* @param forceDownload Always download the keys even if cached.
*/
suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): MXUsersDevicesMap<MXDeviceInfo> {
suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): MXUsersDevicesMap<CryptoDeviceInfo> {
Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds")
// Map from userId -> deviceId -> MXDeviceInfo
val stored = MXUsersDevicesMap<MXDeviceInfo>()
val stored = MXUsersDevicesMap<CryptoDeviceInfo>()
// List of user ids we need to download keys for
val downloadUsers = ArrayList<String>()
@@ -265,7 +299,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
*
* @param downloadUsers the user ids list
*/
private suspend fun doKeyDownloadForUsers(downloadUsers: MutableList<String>): MXUsersDevicesMap<MXDeviceInfo> {
private suspend fun doKeyDownloadForUsers(downloadUsers: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers")
// get the user ids which did not already trigger a keys download
val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }
@@ -283,39 +317,62 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
for (userId in filteredUsers) {
val devices = response.deviceKeys?.get(userId)
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $devices")
if (devices != null) {
val mutableDevices = devices.toMutableMap()
for ((deviceId, deviceInfo) in devices) {
// al devices =
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }
?.toMutableMap()
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $models")
if (!models.isNullOrEmpty()) {
for ((deviceId, deviceInfo) in models) {
// Get the potential previously store device keys for this device
val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(deviceId, userId)
val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(userId, deviceId)
// in some race conditions (like unit tests)
// the self device must be seen as verified
if (deviceInfo.deviceId == credentials.deviceId && userId == credentials.userId) {
deviceInfo.verified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED
deviceInfo.trustLevel = DeviceTrustLevel(previouslyStoredDeviceKeys?.trustLevel?.crossSigningVerified ?: false, true)
}
// Validate received keys
if (!validateDeviceKeys(deviceInfo, userId, deviceId, previouslyStoredDeviceKeys)) {
// New device keys are not valid. Do not store them
mutableDevices.remove(deviceId)
models.remove(deviceId)
if (null != previouslyStoredDeviceKeys) {
// But keep old validated ones if any
mutableDevices[deviceId] = previouslyStoredDeviceKeys
models[deviceId] = previouslyStoredDeviceKeys
}
} else if (null != previouslyStoredDeviceKeys) {
// The verified status is not sync'ed with hs.
// This is a client side information, valid only for this client.
// So, transfer its previous value
mutableDevices[deviceId]!!.verified = previouslyStoredDeviceKeys.verified
models[deviceId]!!.trustLevel = previouslyStoredDeviceKeys.trustLevel
}
}
// Update the store
// Note that devices which aren't in the response will be removed from the stores
cryptoStore.storeUserDevices(userId, mutableDevices)
cryptoStore.storeUserDevices(userId, models)
}
// Handle cross signing keys update
val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also {
Timber.d("## CrossSigning : Got keys for $userId : MSK ${it?.unpaddedBase64PublicKey}")
}
val selfSigningKey = response.selfSigningKeys?.get(userId)?.toCryptoModel()?.also {
Timber.d("## CrossSigning : Got keys for $userId : SSK ${it.unpaddedBase64PublicKey}")
}
val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also {
Timber.d("## CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}")
}
cryptoStore.storeUserCrossSigningKeys(
userId,
masterKey,
selfSigningKey,
userSigningKey
)
}
// Update devices trust for these users
dispatchDeviceChange(downloadUsers)
return onKeysDownloadSucceed(filteredUsers, response.failures)
}
@@ -329,7 +386,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
* @param previouslyStoredDeviceKeys the device keys we received before for this device
* @return true if succeeds
*/
private fun validateDeviceKeys(deviceKeys: MXDeviceInfo?, userId: String, deviceId: String, previouslyStoredDeviceKeys: MXDeviceInfo?): Boolean {
private fun validateDeviceKeys(deviceKeys: CryptoDeviceInfo?, userId: String, deviceId: String, previouslyStoredDeviceKeys: CryptoDeviceInfo?): Boolean {
if (null == deviceKeys) {
Timber.e("## validateDeviceKeys() : deviceKeys is null from $userId:$deviceId")
return false
@@ -357,14 +414,14 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
val signKeyId = "ed25519:" + deviceKeys.deviceId
val signKey = deviceKeys.keys?.get(signKeyId)
val signKey = deviceKeys.keys[signKeyId]
if (null == signKey) {
Timber.e("## validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no ed25519 key")
return false
}
val signatureMap = deviceKeys.signatures?.get(userId)
val signatureMap = deviceKeys.signatures[userId]
if (null == signatureMap) {
Timber.e("## validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no map for $userId")

View File

@@ -107,7 +107,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
cryptoStore.deleteIncomingRoomKeyRequest(request)
}
// if the device is verified already, share the keys
val device = cryptoStore.getUserDevice(deviceId!!, userId)
val device = cryptoStore.getUserDevice(userId, deviceId!!)
if (device != null) {
if (device.isVerified) {
Timber.v("## processReceivedRoomKeyRequests() : device is already verified: sharing keys")
@@ -122,6 +122,14 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
continue
}
}
// If cross signing is available on account we automatically discard untrust devices request
if (cryptoStore.getMyCrossSigningInfo() != null) {
// At this point the device is unknown, we don't want to bother user with that
cryptoStore.deleteIncomingRoomKeyRequest(request)
continue
}
cryptoStore.storeIncomingRoomKeyRequest(request)
onRoomKeyRequest(request)
}

View File

@@ -141,6 +141,7 @@ internal class MXOlmDevice @Inject constructor(
*/
fun release() {
olmAccount?.releaseAccount()
olmUtility?.releaseUtility()
}
/**

View File

@@ -17,7 +17,8 @@
package im.vector.matrix.android.internal.crypto
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.session.SessionScope
import javax.inject.Inject
@@ -35,11 +36,13 @@ internal class MyDeviceInfoHolder @Inject constructor(
/**
* my device info
*/
val myDevice: MXDeviceInfo = MXDeviceInfo(credentials.deviceId!!, credentials.userId)
val myDevice: CryptoDeviceInfo
init {
val keys = HashMap<String, String>()
// TODO it's a bit strange, why not load from DB?
if (!olmDevice.deviceEd25519Key.isNullOrEmpty()) {
keys["ed25519:" + credentials.deviceId] = olmDevice.deviceEd25519Key!!
}
@@ -48,10 +51,22 @@ internal class MyDeviceInfoHolder @Inject constructor(
keys["curve25519:" + credentials.deviceId] = olmDevice.deviceCurve25519Key!!
}
myDevice.keys = keys
// myDevice.keys = keys
//
// myDevice.algorithms = MXCryptoAlgorithms.supportedAlgorithms()
myDevice.algorithms = MXCryptoAlgorithms.supportedAlgorithms()
myDevice.verified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED
// TODO hwo to really check cross signed status?
//
val crossSigned = cryptoStore.getMyCrossSigningInfo()?.masterKey()?.trustLevel?.locallyVerified ?: false
// myDevice.trustLevel = DeviceTrustLevel(crossSigned, true)
myDevice = CryptoDeviceInfo(
credentials.deviceId!!,
credentials.userId,
keys = keys,
algorithms = MXCryptoAlgorithms.supportedAlgorithms(),
trustLevel = DeviceTrustLevel(crossSigned, true)
)
// Add our own deviceinfo to the store
val endToEndDevicesForUser = cryptoStore.getUserDevices(credentials.userId)

View File

@@ -17,7 +17,7 @@
package im.vector.matrix.android.internal.crypto.actions
import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXKey
import im.vector.matrix.android.internal.crypto.model.MXOlmSessionResult
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
@@ -28,8 +28,8 @@ import javax.inject.Inject
internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val olmDevice: MXOlmDevice,
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) {
suspend fun handle(devicesByUser: Map<String, List<MXDeviceInfo>>): MXUsersDevicesMap<MXOlmSessionResult> {
val devicesWithoutSession = ArrayList<MXDeviceInfo>()
suspend fun handle(devicesByUser: Map<String, List<CryptoDeviceInfo>>): MXUsersDevicesMap<MXOlmSessionResult> {
val devicesWithoutSession = ArrayList<CryptoDeviceInfo>()
val results = MXUsersDevicesMap<MXOlmSessionResult>()
@@ -102,7 +102,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
return results
}
private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: MXDeviceInfo): String? {
private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: CryptoDeviceInfo): String? {
var sessionId: String? = null
val deviceId = deviceInfo.deviceId

View File

@@ -40,7 +40,7 @@ internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val o
// Don't bother setting up session to ourself
it.identityKey() != olmDevice.deviceCurve25519Key
// Don't bother setting up sessions with blocked users
&& !it.isVerified
&& !(it.trustLevel?.isVerified() ?: false)
}
}
return ensureOlmSessionsForDevicesAction.handle(devicesByUser)

View File

@@ -19,7 +19,7 @@ package im.vector.matrix.android.internal.crypto.actions
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_OLM
import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.EncryptedMessage
import im.vector.matrix.android.internal.util.JsonCanonicalizer
import im.vector.matrix.android.internal.util.convertToUTF8
@@ -37,7 +37,7 @@ internal class MessageEncrypter @Inject constructor(private val credentials: Cre
* @param deviceInfos list of device infos to encrypt for.
* @return the content for an m.room.encrypted event.
*/
fun encryptMessage(payloadFields: Map<String, Any>, deviceInfos: List<MXDeviceInfo>): EncryptedMessage {
fun encryptMessage(payloadFields: Map<String, Any>, deviceInfos: List<CryptoDeviceInfo>): EncryptedMessage {
val deviceInfoParticipantKey = deviceInfos.associateBy { it.identityKey()!! }
val payloadJson = payloadFields.toMutableMap()

View File

@@ -16,6 +16,7 @@
package im.vector.matrix.android.internal.crypto.actions
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.di.UserId
@@ -27,8 +28,8 @@ internal class SetDeviceVerificationAction @Inject constructor(
@UserId private val userId: String,
private val keysBackup: KeysBackup) {
fun handle(verificationStatus: Int, deviceId: String, userId: String) {
val device = cryptoStore.getUserDevice(deviceId, userId)
fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
val device = cryptoStore.getUserDevice(userId, deviceId)
// Sanity check
if (null == device) {
@@ -36,10 +37,7 @@ internal class SetDeviceVerificationAction @Inject constructor(
return
}
if (device.verified != verificationStatus) {
device.verified = verificationStatus
cryptoStore.storeUserDevice(userId, device)
if (device.isVerified != trustLevel.isVerified()) {
if (userId == this.userId) {
// If one of the user's own devices is being marked as verified / unverified,
// check the key backup status, since whether or not we use this depends on
@@ -47,5 +45,10 @@ internal class SetDeviceVerificationAction @Inject constructor(
keysBackup.checkAndStartKeysBackup()
}
}
if (device.trustLevel != trustLevel) {
device.trustLevel = trustLevel
cryptoStore.storeUserDevice(userId, device)
}
}
}

View File

@@ -21,7 +21,12 @@ import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.internal.crypto.*
import im.vector.matrix.android.internal.crypto.DeviceListManager
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.NewSessionListener
import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequestManager
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting
@@ -59,7 +64,10 @@ internal class MXMegolmDecryption(private val userId: String,
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return decryptEvent(event, timeline, true)
// If cross signing is enabled, we don't send request until the keys are trusted
// There could be a race effect here when xsigning is enabled, we should ensure that keys was downloaded once
val requestOnFail = cryptoStore.getMyCrossSigningInfo()?.isTrusted() == true
return decryptEvent(event, timeline, requestOnFail)
}
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
@@ -297,7 +305,7 @@ internal class MXMegolmDecryption(private val userId: String,
runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
.mapCatching {
val deviceId = request.deviceId
val deviceInfo = cryptoStore.getUserDevice(deviceId ?: "", userId)
val deviceInfo = cryptoStore.getUserDevice(userId, deviceId ?: "")
if (deviceInfo == null) {
throw RuntimeException()
} else {

View File

@@ -29,7 +29,7 @@ import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevi
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.repository.WarnOnUnknownDeviceRepository
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
@@ -95,7 +95,7 @@ internal class MXMegolmEncryption(
*
* @param devicesInRoom the devices list
*/
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): MXOutboundSessionInfo {
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<CryptoDeviceInfo>): MXOutboundSessionInfo {
var session = outboundSession
if (session == null
// Need to make a brand new session?
@@ -106,7 +106,7 @@ internal class MXMegolmEncryption(
outboundSession = session
}
val safeSession = session
val shareMap = HashMap<String, MutableList<MXDeviceInfo>>()/* userId */
val shareMap = HashMap<String, MutableList<CryptoDeviceInfo>>()/* userId */
val userIds = devicesInRoom.userIds
for (userId in userIds) {
val deviceIds = devicesInRoom.getUserDeviceIds(userId)
@@ -129,14 +129,14 @@ internal class MXMegolmEncryption(
* @param devicesByUsers the devices map
*/
private suspend fun shareKey(session: MXOutboundSessionInfo,
devicesByUsers: Map<String, List<MXDeviceInfo>>) {
devicesByUsers: Map<String, List<CryptoDeviceInfo>>) {
// nothing to send, the task is done
if (devicesByUsers.isEmpty()) {
Timber.v("## shareKey() : nothing more to do")
return
}
// reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user)
val subMap = HashMap<String, List<MXDeviceInfo>>()
val subMap = HashMap<String, List<CryptoDeviceInfo>>()
var devicesCount = 0
for ((userId, devices) in devicesByUsers) {
subMap[userId] = devices
@@ -158,7 +158,7 @@ internal class MXMegolmEncryption(
* @param devicesByUser the devices map
*/
private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo,
devicesByUser: Map<String, List<MXDeviceInfo>>) {
devicesByUser: Map<String, List<CryptoDeviceInfo>>) {
val sessionKey = olmDevice.getSessionKey(session.sessionId)
val chainIndex = olmDevice.getMessageIndex(session.sessionId)
@@ -262,7 +262,7 @@ internal class MXMegolmEncryption(
*
* @param userIds the user ids whose devices must be checked.
*/
private suspend fun getDevicesInRoom(userIds: List<String>): MXUsersDevicesMap<MXDeviceInfo> {
private suspend fun getDevicesInRoom(userIds: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
// We are happy to use a cached version here: we assume that if we already
// have a list of the user's devices, then we already share an e2e room
// with them, which means that they will have announced any new devices via
@@ -271,8 +271,8 @@ internal class MXMegolmEncryption(
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>()
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
val devicesInRoom = MXUsersDevicesMap<CryptoDeviceInfo>()
val unknownDevices = MXUsersDevicesMap<CryptoDeviceInfo>()
for (userId in keys.userIds) {
val deviceIds = keys.getUserDeviceIds(userId) ?: continue

View File

@@ -17,7 +17,7 @@
package im.vector.matrix.android.internal.crypto.algorithms.megolm
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import timber.log.Timber
@@ -52,7 +52,7 @@ internal class MXOutboundSessionInfo(
* @param devicesInRoom the devices map
* @return true if we have shared the session with devices which aren't in devicesInRoom.
*/
fun sharedWithTooManyDevices(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): Boolean {
fun sharedWithTooManyDevices(devicesInRoom: MXUsersDevicesMap<CryptoDeviceInfo>): Boolean {
val userIds = sharedWithDevices.userIds
for (userId in userIds) {

View File

@@ -25,7 +25,7 @@ import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForUsersAction
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
internal class MXOlmEncryption(
@@ -42,7 +42,7 @@ internal class MXOlmEncryption(
//
// TODO: there is a race condition here! What if a new user turns up
ensureSession(userIds)
val deviceInfos = ArrayList<MXDeviceInfo>()
val deviceInfos = ArrayList<CryptoDeviceInfo>()
for (userId in userIds) {
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList()
for (device in devices) {

View File

@@ -65,6 +65,33 @@ internal interface CryptoApi {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/query")
fun downloadKeysForUsers(@Body params: KeysQueryBody): Call<KeysQueryResponse>
/**
* CrossSigning - Uploading signing keys
* Public keys for the cross-signing keys are uploaded to the servers using /keys/device_signing/upload.
* This endpoint requires UI Auth.
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/device_signing/upload")
fun uploadSigningKeys(@Body params: UploadSigningKeysBody): Call<KeysQueryResponse>
/**
* CrossSigning - Uploading signatures
* Signatures of device keys can be up
* loaded using /keys/signatures/upload.
* For example, Alice signs one of her devices (HIJKLMN) (using her self-signing key),
* her own master key (using her HIJKLMN device), Bob's master key (using her user-signing key).
*
* The response contains a failures property, which is a map of user ID to device ID to failure reason, if any of the uploaded keys failed.
* The homeserver should verify that the signatures on the uploaded keys are valid.
* If a signature is not valid, the homeserver should set the corresponding entry in failures to a JSON object
* with the errcode property set to M_INVALID_SIGNATURE.
*
* After Alice uploads a signature for her own devices or master key,
* her signature will be included in the results of the /keys/query request when anyone requests her keys.
* However, signatures made for other users' keys, made by her user-signing key, will not be included.
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/signatures/upload")
fun uploadSignatures(@Body params: Map<String, @JvmSuppressWildcards Any>?): Call<SignatureUploadResponse>
/**
* Claim one-time keys.
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-keys-claim

View File

@@ -0,0 +1,693 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.crosssigning
import androidx.lifecycle.LiveData
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
import im.vector.matrix.android.api.extensions.orFalse
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.crypto.DeviceListManager
import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
import im.vector.matrix.android.internal.crypto.model.KeyUsage
import im.vector.matrix.android.internal.crypto.model.rest.UploadSignatureQueryBuilder
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.UploadSignaturesTask
import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.TaskConstraints
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.JsonCanonicalizer
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.withoutPrefix
import kotlinx.coroutines.CoroutineScope
import org.matrix.olm.OlmPkSigning
import org.matrix.olm.OlmUtility
import timber.log.Timber
import javax.inject.Inject
@SessionScope
internal class DefaultCrossSigningService @Inject constructor(
@UserId private val userId: String,
private val cryptoStore: IMXCryptoStore,
private val myDeviceInfoHolder: Lazy<MyDeviceInfoHolder>,
private val olmDevice: MXOlmDevice,
private val deviceListManager: DeviceListManager,
private val uploadSigningKeysTask: UploadSigningKeysTask,
private val uploadSignaturesTask: UploadSignaturesTask,
private val cryptoCoroutineScope: CoroutineScope,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor) : CrossSigningService, DeviceListManager.UserDevicesUpdateListener {
private var olmUtility: OlmUtility? = null
private var masterPkSigning: OlmPkSigning? = null
private var userPkSigning: OlmPkSigning? = null
private var selfSigningPkSigning: OlmPkSigning? = null
init {
try {
olmUtility = OlmUtility()
// Try to get stored keys if they exist
cryptoStore.getMyCrossSigningInfo()?.let { mxCrossSigningInfo ->
Timber.i("## CrossSigning - Found Existing self signed keys")
Timber.i("## CrossSigning - Checking if private keys are known")
cryptoStore.getCrossSigningPrivateKeys()?.let { privateKeysInfo ->
privateKeysInfo.master
?.fromBase64NoPadding()
?.let { privateKeySeed ->
val pkSigning = OlmPkSigning()
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
masterPkSigning = pkSigning
Timber.i("## CrossSigning - Loading master key success")
} else {
Timber.w("## CrossSigning - Public master key does not match the private key")
// TODO untrust
}
}
privateKeysInfo.user
?.fromBase64NoPadding()
?.let { privateKeySeed ->
val pkSigning = OlmPkSigning()
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
userPkSigning = pkSigning
Timber.i("## CrossSigning - Loading User Signing key success")
} else {
Timber.w("## CrossSigning - Public User key does not match the private key")
// TODO untrust
}
}
privateKeysInfo.selfSigned
?.fromBase64NoPadding()
?.let { privateKeySeed ->
val pkSigning = OlmPkSigning()
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
selfSigningPkSigning = pkSigning
Timber.i("## CrossSigning - Loading Self Signing key success")
} else {
Timber.w("## CrossSigning - Public Self Signing key does not match the private key")
// TODO untrust
}
}
}
// Recover local trust in case private key are there?
setUserKeysAsTrusted(userId, checkUserTrust(userId).isVerified())
}
} catch (e: Throwable) {
// Mmm this kind of a big issue
Timber.e(e, "Failed to initialize Cross Signing")
}
deviceListManager.addListener(this)
}
fun release() {
olmUtility?.releaseUtility()
listOf(masterPkSigning, userPkSigning, selfSigningPkSigning).forEach { it?.releaseSigning() }
deviceListManager.removeListener(this)
}
protected fun finalize() {
release()
}
/**
* - Make 3 key pairs (MSK, USK, SSK)
* - Save the private keys with proper security
* - Sign the keys and upload them
* - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures
*/
override fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback<Unit>?) {
Timber.d("## CrossSigning initializeCrossSigning")
// =================
// MASTER KEY
// =================
val masterPkOlm = OlmPkSigning()
val masterKeyPrivateKey = OlmPkSigning.generateSeed()
val masterPublicKey = masterPkOlm.initWithSeed(masterKeyPrivateKey)
Timber.v("## CrossSigning - masterPublicKey:$masterPublicKey")
// =================
// USER KEY
// =================
val userSigningPkOlm = OlmPkSigning()
val uskPrivateKey = OlmPkSigning.generateSeed()
val uskPublicKey = userSigningPkOlm.initWithSeed(uskPrivateKey)
Timber.v("## CrossSigning - uskPublicKey:$uskPublicKey")
// Sign userSigningKey with master
val signedUSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.USER_SIGNING)
.key(uskPublicKey)
.build()
.canonicalSignable()
.let { masterPkOlm.sign(it) }
// =================
// SELF SIGNING KEY
// =================
val selfSigningPkOlm = OlmPkSigning()
val sskPrivateKey = OlmPkSigning.generateSeed()
val sskPublicKey = selfSigningPkOlm.initWithSeed(sskPrivateKey)
Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey")
// Sign userSigningKey with master
val signedSSK = JsonCanonicalizer.getCanonicalJson(Map::class.java, CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING)
.key(sskPublicKey)
.build().signalableJSONDictionary()).let { masterPkOlm.sign(it) }
// I need to upload the keys
val mskCrossSigningKeyInfo = CryptoCrossSigningKey.Builder(userId, KeyUsage.MASTER)
.key(masterPublicKey)
.build()
val params = UploadSigningKeysTask.Params(
masterKey = mskCrossSigningKeyInfo,
userKey = CryptoCrossSigningKey.Builder(userId, KeyUsage.USER_SIGNING)
.key(uskPublicKey)
.signature(userId, masterPublicKey, signedUSK)
.build(),
selfSignedKey = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING)
.key(sskPublicKey)
.signature(userId, masterPublicKey, signedSSK)
.build(),
userPasswordAuth = authParams
)
this.masterPkSigning = masterPkOlm
this.userPkSigning = userSigningPkOlm
this.selfSigningPkSigning = selfSigningPkOlm
val crossSigningInfo = MXCrossSigningInfo(userId, listOf(params.masterKey, params.userKey, params.selfSignedKey))
cryptoStore.setMyCrossSigningInfo(crossSigningInfo)
setUserKeysAsTrusted(userId, true)
cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey?.toBase64NoPadding(), uskPrivateKey?.toBase64NoPadding(), sskPrivateKey?.toBase64NoPadding())
uploadSigningKeysTask.configureWith(params) {
this.constraints = TaskConstraints(true)
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.i("## CrossSigning - Keys successfully uploaded")
// Sign the current device with SSK
val uploadSignatureQueryBuilder = UploadSignatureQueryBuilder()
val myDevice = myDeviceInfoHolder.get().myDevice
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary())
val signedDevice = selfSigningPkOlm.sign(canonicalJson)
val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap()).also {
it[userId] = (it[userId]
?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice)
}
myDevice.copy(signatures = updateSignatures).let {
uploadSignatureQueryBuilder.withDeviceInfo(it)
}
// sign MSK with device key (migration) and upload signatures
olmDevice.signMessage(JsonCanonicalizer.getCanonicalJson(Map::class.java, mskCrossSigningKeyInfo.signalableJSONDictionary()))?.let { sign ->
val mskUpdatedSignatures = (mskCrossSigningKeyInfo.signatures?.toMutableMap()
?: HashMap()).also {
it[userId] = (it[userId]
?: HashMap()) + mapOf("ed25519:${myDevice.deviceId}" to sign)
}
mskCrossSigningKeyInfo.copy(
signatures = mskUpdatedSignatures
).let {
uploadSignatureQueryBuilder.withSigningKeyInfo(it)
}
}
resetTrustOnKeyChange()
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadSignatureQueryBuilder.build())) {
// this.retryCount = 3
this.constraints = TaskConstraints(true)
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.i("## CrossSigning - signatures successfully uploaded")
callback?.onSuccess(Unit)
}
override fun onFailure(failure: Throwable) {
// Clear
Timber.e(failure, "## CrossSigning - Failed to upload signatures")
clearSigningKeys()
}
}
}.executeBy(taskExecutor)
}
override fun onFailure(failure: Throwable) {
Timber.e(failure, "## CrossSigning - Failed to upload signing keys")
clearSigningKeys()
callback?.onFailure(failure)
}
}
}.executeBy(taskExecutor)
}
private fun clearSigningKeys() {
masterPkSigning?.releaseSigning()
userPkSigning?.releaseSigning()
selfSigningPkSigning?.releaseSigning()
masterPkSigning = null
userPkSigning = null
selfSigningPkSigning = null
cryptoStore.setMyCrossSigningInfo(null)
cryptoStore.storePrivateKeysInfo(null, null, null)
}
private fun resetTrustOnKeyChange() {
Timber.i("## CrossSigning - Clear all other user trust")
cryptoStore.clearOtherUserTrust()
}
/**
*
* ┏━━━━━━━━┓ ┏━━━━━━━━┓
* ┃ ALICE ┃ ┃ BOB ┃
* ┗━━━━━━━━┛ ┗━━━━━━━━┛
* MSK ┌────────────▶ MSK
* │
* │ │
* │ SSK │
* │ │
* │ │
* └──▶ USK ────────────┘
*/
override fun isUserTrusted(otherUserId: String): Boolean {
return cryptoStore.getCrossSigningInfo(userId)?.isTrusted() == true
}
override fun isCrossSigningVerified(): Boolean {
return checkSelfTrust().isVerified()
}
/**
* Will not force a download of the key, but will verify signatures trust chain
*/
override fun checkUserTrust(otherUserId: String): UserTrustResult {
Timber.d("## CrossSigning checkUserTrust for $otherUserId")
if (otherUserId == userId) {
return checkSelfTrust()
}
// I trust a user if I trust his master key
// I can trust the master key if it is signed by my user key
// TODO what if the master key is signed by a device key that i have verified
// First let's get my user key
val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId)
val myUserKey = myCrossSigningInfo?.userKey()
?: return UserTrustResult.CrossSigningNotConfigured(userId)
if (!myCrossSigningInfo.isTrusted()) {
return UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
}
// Let's get the other user master key
val otherMasterKey = cryptoStore.getCrossSigningInfo(otherUserId)?.masterKey()
?: return UserTrustResult.UnknownCrossSignatureInfo(otherUserId)
val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures
?.get(userId) // Signatures made by me
?.get("ed25519:${myUserKey.unpaddedBase64PublicKey}")
if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) {
Timber.d("## CrossSigning checkUserTrust false for $otherUserId, not signed by my UserSigningKey")
return UserTrustResult.KeyNotSigned(otherMasterKey)
}
// Check that Alice USK signature of Bob MSK is valid
try {
olmUtility!!.verifyEd25519Signature(masterKeySignaturesMadeByMyUserKey, myUserKey.unpaddedBase64PublicKey, otherMasterKey.canonicalSignable())
} catch (failure: Throwable) {
return UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey)
}
return UserTrustResult.Success
}
private fun checkSelfTrust(): UserTrustResult {
// Special case when it's me,
// 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)
val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(userId)
val myMasterKey = myCrossSigningInfo?.masterKey()
?: return UserTrustResult.CrossSigningNotConfigured(userId)
// Is the master key trusted
// 1) check if I know the private key
val masterPrivateKey = cryptoStore.getCrossSigningPrivateKeys()
?.master
?.fromBase64NoPadding()
var isMaterKeyTrusted = false
if (masterPrivateKey != null) {
// Check if private match public
var olmPkSigning: OlmPkSigning? = null
try {
olmPkSigning = OlmPkSigning()
val expectedPK = olmPkSigning.initWithSeed(masterPrivateKey)
isMaterKeyTrusted = myMasterKey.unpaddedBase64PublicKey == expectedPK
} catch (failure: Throwable) {
Timber.e(failure)
}
olmPkSigning?.releaseSigning()
} else {
// Maybe it's signed by a locally trusted device?
myMasterKey.signatures?.get(userId)?.forEach { (key, value) ->
val potentialDeviceId = key.withoutPrefix("ed25519:")
val potentialDevice = cryptoStore.getUserDevice(userId, potentialDeviceId)
if (potentialDevice != null && potentialDevice.isVerified) {
// Check signature validity?
try {
olmUtility?.verifyEd25519Signature(value, potentialDevice.fingerprint(), myMasterKey.canonicalSignable())
isMaterKeyTrusted = true
return@forEach
} catch (failure: Throwable) {
// log
Timber.v(failure)
}
}
}
}
if (!isMaterKeyTrusted) {
return UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
}
val myUserKey = myCrossSigningInfo.userKey()
?: return UserTrustResult.CrossSigningNotConfigured(userId)
val userKeySignaturesMadeByMyMasterKey = myUserKey.signatures
?.get(userId) // Signatures made by me
?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}")
if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
Timber.d("## CrossSigning checkUserTrust false for $userId, USK not signed by MSK")
return UserTrustResult.KeyNotSigned(myUserKey)
}
// Check that Alice USK signature of Alice MSK is valid
try {
olmUtility!!.verifyEd25519Signature(userKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, myUserKey.canonicalSignable())
} catch (failure: Throwable) {
return UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey)
}
val mySSKey = myCrossSigningInfo.selfSigningKey()
?: return UserTrustResult.CrossSigningNotConfigured(userId)
val ssKeySignaturesMadeByMyMasterKey = mySSKey.signatures
?.get(userId) // Signatures made by me
?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}")
if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
Timber.d("## CrossSigning checkUserTrust false for $userId, SSK not signed by MSK")
return UserTrustResult.KeyNotSigned(mySSKey)
}
// Check that Alice USK signature of Alice MSK is valid
try {
olmUtility!!.verifyEd25519Signature(ssKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, mySSKey.canonicalSignable())
} catch (failure: Throwable) {
return UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey)
}
return UserTrustResult.Success
}
override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
return cryptoStore.getCrossSigningInfo(otherUserId)
}
override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
return cryptoStore.getLiveCrossSigningInfo(userId)
}
override fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
return cryptoStore.getMyCrossSigningInfo()
}
override fun canCrossSign(): Boolean {
return checkSelfTrust().isVerified() && cryptoStore.getCrossSigningPrivateKeys()?.selfSigned != null
}
override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) {
Timber.d("## CrossSigning - Mark user $userId as trusted ")
// We should have this user keys
val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey()
if (otherMasterKeys == null) {
callback.onFailure(Throwable("## CrossSigning - Other master signing key is not known"))
return
}
val myKeys = getUserCrossSigningKeys(userId)
if (myKeys == null) {
callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account"))
return
}
val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey
if (userPubKey == null || userPkSigning == null) {
callback.onFailure(Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey"))
return
}
// Sign the other MasterKey with our UserSigning key
val newSignature = JsonCanonicalizer.getCanonicalJson(Map::class.java,
otherMasterKeys.signalableJSONDictionary()).let { userPkSigning?.sign(it) }
if (newSignature == null) {
// race??
callback.onFailure(Throwable("## CrossSigning - Failed to sign"))
return
}
cryptoStore.setUserKeysAsTrusted(otherUserId, true)
// TODO update local copy with new signature directly here? kind of local echo of trust?
Timber.d("## CrossSigning - Upload signature of $userId MSK signed by USK")
val uploadQuery = UploadSignatureQueryBuilder()
.withSigningKeyInfo(otherMasterKeys.copyForSignature(userId, userPubKey, newSignature))
.build()
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
this.callback = callback
}.executeBy(taskExecutor)
}
override fun signDevice(deviceId: String, callback: MatrixCallback<Unit>) {
// This device should be yours
val device = cryptoStore.getUserDevice(userId, deviceId)
if (device == null) {
callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours"))
return
}
val myKeys = getUserCrossSigningKeys(userId)
if (myKeys == null) {
callback.onFailure(Throwable("CrossSigning is not setup for this account"))
return
}
val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey
if (ssPubKey == null || selfSigningPkSigning == null) {
callback.onFailure(Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey"))
return
}
// Sign with self signing
val newSignature = selfSigningPkSigning?.sign(device.canonicalSignable())
if (newSignature == null) {
// race??
callback.onFailure(Throwable("Failed to sign"))
return
}
val toUpload = device.copy(
signatures = mapOf(
userId
to
mapOf(
"ed25519:$ssPubKey" to newSignature
)
)
)
val uploadQuery = UploadSignatureQueryBuilder()
.withDeviceInfo(toUpload)
.build()
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
this.callback = callback
}.executeBy(taskExecutor)
}
override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
val otherDevice = cryptoStore.getUserDevice(otherUserId, otherDeviceId)
?: return DeviceTrustResult.UnknownDevice(otherDeviceId)
val myKeys = getUserCrossSigningKeys(userId)
?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(userId))
if (!myKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(myKeys))
val otherKeys = getUserCrossSigningKeys(otherUserId)
?: return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.CrossSigningNotConfigured(otherUserId))
// TODO should we force verification ?
if (!otherKeys.isTrusted()) return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.KeysNotTrusted(otherKeys))
// Check if the trust chain is valid
/*
* ┏━━━━━━━━┓ ┏━━━━━━━━┓
* ┃ ALICE ┃ ┃ BOB ┃
* ┗━━━━━━━━┛ ┗━━━━━━━━┛
* MSK ┌────────────▶MSK
* │
* │ │ │
* │ SSK │ └──▶ SSK ──────────────────┐
* │ │ │
* │ │ USK │
* └──▶ USK ────────────┘ (not visible by │
* Alice) │
* ▼
* ┌──────────────┐
* │ BOB's Device │
* └──────────────┘
*/
val otherSSKSignature = otherDevice.signatures?.get(otherUserId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}")
?: return legacyFallbackTrust(
locallyTrusted,
DeviceTrustResult.MissingDeviceSignature(otherDeviceId, otherKeys.selfSigningKey()
?.unpaddedBase64PublicKey
?: ""
)
)
// Check bob's device is signed by bob's SSK
try {
olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable())
} catch (e: Throwable) {
return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDeviceId, otherSSKSignature, e))
}
return DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = true, locallyVerified = locallyTrusted))
}
private fun legacyFallbackTrust(locallyTrusted: Boolean?, crossSignTrustFail: DeviceTrustResult): DeviceTrustResult {
return if (locallyTrusted == true) {
DeviceTrustResult.Success(DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true))
} else {
crossSignTrustFail
}
}
override fun onUsersDeviceUpdate(users: List<String>) {
Timber.d("## CrossSigning - onUsersDeviceUpdate for ${users.size} users")
users.forEach { otherUserId ->
checkUserTrust(otherUserId).let {
Timber.d("## CrossSigning - update trust for $otherUserId , verified=${it.isVerified()}")
setUserKeysAsTrusted(otherUserId, it.isVerified())
}
// TODO if my keys have changes, i should recheck all devices of all users?
val devices = cryptoStore.getUserDeviceList(otherUserId)
devices?.forEach { device ->
val updatedTrust = checkDeviceTrust(otherUserId, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
cryptoStore.setDeviceTrust(otherUserId, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
}
if (otherUserId == userId) {
// It's me, i should check if a newly trusted device is signing my master key
// In this case it will change my MSK trust, and should then re-trigger a check of all other user trust
setUserKeysAsTrusted(otherUserId, checkSelfTrust().isVerified())
}
}
}
private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
// If it's me, recheck trust of all users and devices?
val users = ArrayList<String>()
if (otherUserId == userId && currentTrust != trusted) {
cryptoStore.updateUsersTrust {
users.add(it)
checkUserTrust(it).isVerified()
}
users.forEach {
cryptoStore.getUserDeviceList(it)?.forEach { device ->
val updatedTrust = checkDeviceTrust(it, device.deviceId, device.trustLevel?.isLocallyVerified() ?: false)
Timber.d("## CrossSigning - update trust for device ${device.deviceId} of user $otherUserId , verified=$updatedTrust")
cryptoStore.setDeviceTrust(it, device.deviceId, updatedTrust.isCrossSignedVerified(), updatedTrust.isLocallyVerified())
}
}
}
}
override fun getTrustLevelForUsers(userIds: List<String>): RoomEncryptionTrustLevel {
val allTrusted = userIds
.filter { getUserCrossSigningKeys(it)?.isTrusted() == true }
val allUsersAreVerified = userIds.size == allTrusted.size
return if (allTrusted.isEmpty()) {
RoomEncryptionTrustLevel.Default
} else {
// If one of the verified user as an untrusted device -> warning
// Green if all devices of all verified users are trusted -> green
// else black
val allDevices = allTrusted.mapNotNull {
cryptoStore.getUserDeviceList(it)
}.flatten()
if (getMyCrossSigningKeys() != null) {
val hasWarning = allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() }
if (hasWarning) {
RoomEncryptionTrustLevel.Warning
} else {
if (allUsersAreVerified) RoomEncryptionTrustLevel.Trusted else RoomEncryptionTrustLevel.Default
}
} else {
val hasWarningLegacy = allDevices.any { !it.isVerified }
if (hasWarningLegacy) {
RoomEncryptionTrustLevel.Warning
} else {
if (allUsersAreVerified) RoomEncryptionTrustLevel.Trusted else RoomEncryptionTrustLevel.Default
}
}
}
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.crosssigning
data class DeviceTrustLevel(
val crossSigningVerified: Boolean,
val locallyVerified: Boolean?
) {
fun isVerified() = crossSigningVerified || locallyVerified == true
fun isCrossSigningVerified() = crossSigningVerified
fun isLocallyVerified() = locallyVerified
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.crosssigning
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
sealed class DeviceTrustResult {
data class Success(val level: DeviceTrustLevel) : DeviceTrustResult()
data class UnknownDevice(val deviceID: String) : DeviceTrustResult()
data class CrossSigningNotConfigured(val userID: String) : DeviceTrustResult()
data class KeysNotTrusted(val key: MXCrossSigningInfo) : DeviceTrustResult()
data class MissingDeviceSignature(val deviceId: String, val signingKey: String) : DeviceTrustResult()
data class InvalidDeviceSignature(val deviceId: String, val signingKey: String, val throwable: Throwable?) : DeviceTrustResult()
}
fun DeviceTrustResult.isSuccess() = this is DeviceTrustResult.Success
fun DeviceTrustResult.isCrossSignedVerified() = this is DeviceTrustResult.Success && level.isCrossSigningVerified()
fun DeviceTrustResult.isLocallyVerified() = this is DeviceTrustResult.Success && level.isLocallyVerified() == true

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.crosssigning
import android.util.Base64
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
import im.vector.matrix.android.internal.util.JsonCanonicalizer
fun CryptoDeviceInfo.canonicalSignable(): String {
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
}
fun CryptoCrossSigningKey.canonicalSignable(): String {
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
}
fun ByteArray.toBase64NoPadding(): String {
return Base64.encodeToString(this, Base64.NO_PADDING or Base64.NO_WRAP)
}
fun String.fromBase64NoPadding(): ByteArray {
return Base64.decode(this, Base64.NO_PADDING or Base64.NO_WRAP)
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.crosssigning
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey
sealed class UserTrustResult {
object Success : UserTrustResult()
// data class Success(val deviceID: String, val crossSigned: Boolean) : UserTrustResult()
// data class UnknownDevice(val deviceID: String) : UserTrustResult()
data class CrossSigningNotConfigured(val userID: String) : UserTrustResult()
data class UnknownCrossSignatureInfo(val userID: String) : UserTrustResult()
data class KeysNotTrusted(val key: MXCrossSigningInfo) : UserTrustResult()
data class KeyNotSigned(val key: CryptoCrossSigningKey) : UserTrustResult()
data class InvalidSignature(val key: CryptoCrossSigningKey, val signature: String) : UserTrustResult()
}
fun UserTrustResult.isVerified() = this is UserTrustResult.Success

View File

@@ -402,7 +402,7 @@ internal class KeysBackup @Inject constructor(
}
if (deviceId != null) {
val device = cryptoStore.getUserDevice(deviceId, userId)
val device = cryptoStore.getUserDevice(userId, deviceId)
var isSignatureValid = false
if (device == null) {

View File

@@ -16,7 +16,7 @@
package im.vector.matrix.android.internal.crypto.keysbackup.model
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
/**
* A signature in a the `KeyBackupVersionTrust` object.
@@ -26,7 +26,7 @@ class KeyBackupVersionTrustSignature {
/**
* The device that signed the backup version.
*/
var device: MXDeviceInfo? = null
var device: CryptoDeviceInfo? = null
/**
*Flag to indicate the signature from this device is valid.

View File

@@ -16,7 +16,7 @@
package im.vector.matrix.android.internal.crypto.keysbackup.model
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo
/**
* A signature in a `KeysBackupVersionTrust` object.
@@ -32,7 +32,7 @@ class KeysBackupVersionTrustSignature {
* The device that signed the backup version.
* Can be null if the device is not known.
*/
var device: MXDeviceInfo? = null
var device: CryptoDeviceInfo? = null
/**
* Flag to indicate the signature from this device is valid.

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.model
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.model.rest.RestKeyInfo
data class CryptoCrossSigningKey(
override val userId: String,
val usages: List<String>?,
override val keys: Map<String, String>,
override val signatures: Map<String, Map<String, String>>?,
var trustLevel: DeviceTrustLevel? = null
) : CryptoInfo {
override fun signalableJSONDictionary(): Map<String, Any> {
val map = HashMap<String, Any>()
userId.let { map["user_id"] = it }
usages?.let { map["usage"] = it }
keys.let { map["keys"] = it }
return map
}
val unpaddedBase64PublicKey: String? = keys.values.firstOrNull()
val isMasterKey = usages?.contains(KeyUsage.MASTER.value) ?: false
val isSelfSigningKey = usages?.contains(KeyUsage.SELF_SIGNING.value) ?: false
val isUserKey = usages?.contains(KeyUsage.USER_SIGNING.value) ?: false
fun addSignatureAndCopy(userId: String, signedWithNoPrefix: String, signature: String): CryptoCrossSigningKey {
val updated = (signatures?.toMutableMap() ?: HashMap())
val userMap = updated[userId]?.toMutableMap()
?: HashMap<String, String>().also { updated[userId] = it }
userMap["ed25519:$signedWithNoPrefix"] = signature
return this.copy(
signatures = updated
)
}
fun copyForSignature(userId: String, signedWithNoPrefix: String, signature: String): CryptoCrossSigningKey {
return this.copy(
signatures = mapOf(userId to mapOf("ed25519:$signedWithNoPrefix" to signature))
)
}
data class Builder(
val userId: String,
val usage: KeyUsage,
private var base64Pkey: String? = null,
private val signatures: ArrayList<Triple<String, String, String>> = ArrayList()
) {
fun key(publicKeyBase64: String) = apply {
base64Pkey = publicKeyBase64
}
fun signature(userId: String, keySignedBase64: String, base64Signature: String) = apply {
signatures.add(Triple(userId, keySignedBase64, base64Signature))
}
fun build(): CryptoCrossSigningKey {
val b64key = base64Pkey ?: throw IllegalArgumentException("")
val signMap = HashMap<String, HashMap<String, String>>()
signatures.forEach { info ->
val uMap = signMap[info.first]
?: HashMap<String, String>().also { signMap[info.first] = it }
uMap["ed25519:${info.second}"] = info.third
}
return CryptoCrossSigningKey(
userId = userId,
usages = listOf(usage.value),
keys = mapOf("ed25519:$b64key" to b64key),
signatures = signMap)
}
}
}
enum class KeyUsage(val value: String) {
MASTER("master"),
SELF_SIGNING("self_signing"),
USER_SIGNING("user_signing")
}
internal fun CryptoCrossSigningKey.toRest(): RestKeyInfo {
return CryptoInfoMapper.map(this)
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.model
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustLevel
import im.vector.matrix.android.internal.crypto.model.rest.RestDeviceInfo
import im.vector.matrix.android.internal.crypto.store.db.model.CryptoMapper
import im.vector.matrix.android.internal.crypto.store.db.model.DeviceInfoEntity
data class CryptoDeviceInfo(
val deviceId: String,
override val userId: String,
var algorithms: List<String>? = null,
override val keys: Map<String, String>? = null,
override val signatures: Map<String, Map<String, String>>? = null,
val unsigned: JsonDict? = null,
// TODO how to store if this device is verified by a user SSK, or is legacy trusted?
// I need to know if it is trusted via cross signing (Trusted because bob verified it)
var trustLevel: DeviceTrustLevel? = null,
var isBlocked: Boolean = false
) : CryptoInfo {
val isVerified: Boolean
get() = trustLevel?.isVerified() ?: false
val isUnknown: Boolean
get() = trustLevel == null
/**
* @return the fingerprint
*/
fun fingerprint(): String? {
return keys
?.takeIf { !deviceId.isBlank() }
?.get("ed25519:$deviceId")
}
/**
* @return the identity key
*/
fun identityKey(): String? {
return keys
?.takeIf { !deviceId.isBlank() }
?.get("curve25519:$deviceId")
}
/**
* @return the display name
*/
fun displayName(): String? {
return unsigned?.get("device_display_name") as? String
}
override fun signalableJSONDictionary(): Map<String, Any> {
val map = HashMap<String, Any>()
map["device_id"] = deviceId
map["user_id"] = userId
algorithms?.let { map["algorithms"] = it }
keys?.let { map["keys"] = it }
return map
}
//
// /**
// * @return a dictionary of the parameters
// */
// fun toDeviceKeys(): DeviceKeys {
// return DeviceKeys(
// userId = userId,
// deviceId = deviceId,
// algorithms = algorithms!!,
// keys = keys!!,
// signatures = signatures!!
// )
// }
}
internal fun CryptoDeviceInfo.toRest(): RestDeviceInfo {
return CryptoInfoMapper.map(this)
}
internal fun CryptoDeviceInfo.toEntity(): DeviceInfoEntity {
return CryptoMapper.mapToEntity(this)
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.model
/**
* Generic crypto info.
* Can be a device (CryptoDeviceInfo), as well as a CryptoCrossSigningInfo (can be seen as a kind of virtual device)
*/
interface CryptoInfo {
val userId: String
val keys: Map<String, String>?
val signatures: Map<String, Map<String, String>>?
fun signalableJSONDictionary(): Map<String, Any>
}

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.model
import im.vector.matrix.android.internal.crypto.model.rest.RestDeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.RestKeyInfo
internal object CryptoInfoMapper {
fun map(restDeviceInfo: RestDeviceInfo): CryptoDeviceInfo {
return CryptoDeviceInfo(
deviceId = restDeviceInfo.deviceId,
userId = restDeviceInfo.userId,
algorithms = restDeviceInfo.algorithms,
keys = restDeviceInfo.keys,
signatures = restDeviceInfo.signatures,
unsigned = restDeviceInfo.unsigned,
trustLevel = null
)
}
fun map(cryptoDeviceInfo: CryptoDeviceInfo): RestDeviceInfo {
return RestDeviceInfo(
deviceId = cryptoDeviceInfo.deviceId,
algorithms = cryptoDeviceInfo.algorithms,
keys = cryptoDeviceInfo.keys,
signatures = cryptoDeviceInfo.signatures,
unsigned = cryptoDeviceInfo.unsigned,
userId = cryptoDeviceInfo.userId
)
}
fun map(keyInfo: RestKeyInfo): CryptoCrossSigningKey {
return CryptoCrossSigningKey(
userId = keyInfo.userId,
usages = keyInfo.usages,
keys = keyInfo.keys ?: emptyMap(),
signatures = keyInfo.signatures,
trustLevel = null
)
}
fun map(keyInfo: CryptoCrossSigningKey): RestKeyInfo {
return RestKeyInfo(
userId = keyInfo.userId,
usages = keyInfo.usages,
keys = keyInfo.keys,
signatures = keyInfo.signatures
)
}
fun RestDeviceInfo.toCryptoModel(): CryptoDeviceInfo {
return map(this)
}
fun CryptoDeviceInfo.toRest(): RestDeviceInfo {
return map(this)
}
// fun RestKeyInfo.toCryptoModel(): CryptoCrossSigningKey {
// return map(this)
// }
fun CryptoCrossSigningKey.toRest(): RestKeyInfo {
return map(this)
}
}

View File

@@ -22,7 +22,7 @@ data class MXOlmSessionResult(
/**
* the device
*/
val deviceInfo: MXDeviceInfo,
val deviceInfo: CryptoDeviceInfo,
/**
* Base64 olm session id.
* null if no session could be established.

View File

@@ -24,5 +24,5 @@ import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
internal data class DeleteDeviceParams(
@Json(name = "auth")
var deleteDeviceAuth: DeleteDeviceAuth? = null
val userPasswordAuth: UserPasswordAuth? = null
)

View File

@@ -18,22 +18,24 @@ package im.vector.matrix.android.internal.crypto.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.util.JsonDict
@JsonClass(generateAdapter = true)
data class DeviceKeys(
@Json(name = "user_id")
val userId: String,
val userId: String?,
@Json(name = "device_id")
val deviceId: String,
val deviceId: String?,
@Json(name = "algorithms")
val algorithms: List<String>,
val algorithms: List<String>?,
@Json(name = "keys")
val keys: Map<String, String>,
val keys: Map<String, String>?,
@Json(name = "signatures")
val signatures: JsonDict
val signatures: Map<String, Map<String, String>>?,
@Json(name = "usage")
val usage: List<String>? = null
)

View File

@@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass
* This class describes the key changes response
*/
@JsonClass(generateAdapter = true)
data class KeyChangesResponse(
internal data class KeyChangesResponse(
// list of user ids which have new devices
@Json(name = "changed")
var changed: List<String>? = null,

View File

@@ -17,13 +17,15 @@ package im.vector.matrix.android.internal.crypto.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAccept
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoAcceptFactory
import timber.log.Timber
/**
* Sent by Bob to accept a verification from a previously sent m.key.verification.start message.
*/
@JsonClass(generateAdapter = true)
data class KeyVerificationAccept(
internal data class KeyVerificationAccept(
/**
* string to identify the transaction.
@@ -31,39 +33,41 @@ data class KeyVerificationAccept(
* Alices device should record this ID and use it in future messages in this transaction.
*/
@Json(name = "transaction_id")
var transactionID: String? = null,
override val transactionID: String? = null,
/**
* The key agreement protocol that Bobs device has selected to use, out of the list proposed by Alices device
*/
@Json(name = "key_agreement_protocol")
var keyAgreementProtocol: String? = null,
override val keyAgreementProtocol: String? = null,
/**
* The hash algorithm that Bobs device has selected to use, out of the list proposed by Alices device
*/
var hash: String? = null,
@Json(name = "hash")
override val hash: String? = null,
/**
* The message authentication code that Bobs device has selected to use, out of the list proposed by Alices device
*/
@Json(name = "message_authentication_code")
var messageAuthenticationCode: String? = null,
override val messageAuthenticationCode: String? = null,
/**
* An array of short authentication string methods that Bobs client (and Bob) understands. Must be a subset of the list proposed by Alices device
*/
@Json(name = "short_authentication_string")
var shortAuthenticationStrings: List<String>? = null,
override val shortAuthenticationStrings: List<String>? = null,
/**
* The hash (encoded as unpadded base64) of the concatenation of the devices ephemeral public key (QB, encoded as unpadded base64)
* and the canonical JSON representation of the m.key.verification.start message.
*/
var commitment: String? = null
) : SendToDeviceObject {
@Json(name = "commitment")
override var commitment: String? = null
) : SendToDeviceObject, VerificationInfoAccept {
fun isValid(): Boolean {
override fun isValid(): Boolean {
if (transactionID.isNullOrBlank()
|| keyAgreementProtocol.isNullOrBlank()
|| hash.isNullOrBlank()
@@ -76,21 +80,23 @@ data class KeyVerificationAccept(
return true
}
companion object {
fun create(tid: String,
keyAgreementProtocol: String,
hash: String,
commitment: String,
messageAuthenticationCode: String,
shortAuthenticationStrings: List<String>): KeyVerificationAccept {
return KeyVerificationAccept().apply {
this.transactionID = tid
this.keyAgreementProtocol = keyAgreementProtocol
this.hash = hash
this.commitment = commitment
this.messageAuthenticationCode = messageAuthenticationCode
this.shortAuthenticationStrings = shortAuthenticationStrings
}
override fun toSendToDeviceObject() = this
companion object : VerificationInfoAcceptFactory {
override fun create(tid: String,
keyAgreementProtocol: String,
hash: String,
commitment: String,
messageAuthenticationCode: String,
shortAuthenticationStrings: List<String>): VerificationInfoAccept {
return KeyVerificationAccept(
transactionID = tid,
keyAgreementProtocol = keyAgreementProtocol,
hash = hash,
commitment = commitment,
messageAuthenticationCode = messageAuthenticationCode,
shortAuthenticationStrings = shortAuthenticationStrings
)
}
}
}

View File

@@ -18,40 +18,43 @@ package im.vector.matrix.android.internal.crypto.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoCancel
/**
* To device event sent by either party to cancel a key verification.
*/
@JsonClass(generateAdapter = true)
data class KeyVerificationCancel(
internal data class KeyVerificationCancel(
/**
* the transaction ID of the verification to cancel
*/
@Json(name = "transaction_id")
var transactionID: String? = null,
override val transactionID: String? = null,
/**
* machine-readable reason for cancelling, see #CancelCode
*/
var code: String? = null,
override val code: String? = null,
/**
* human-readable reason for cancelling. This should only be used if the receiving client does not understand the code given.
*/
var reason: String? = null
) : SendToDeviceObject {
override val reason: String? = null
) : SendToDeviceObject, VerificationInfoCancel {
companion object {
fun create(tid: String, cancelCode: CancelCode): KeyVerificationCancel {
return KeyVerificationCancel().apply {
this.transactionID = tid
this.code = cancelCode.value
this.reason = cancelCode.humanReadable
}
return KeyVerificationCancel(
tid,
cancelCode.value,
cancelCode.humanReadable
)
}
}
fun isValid(): Boolean {
override fun toSendToDeviceObject() = this
override fun isValid(): Boolean {
if (transactionID.isNullOrBlank() || code.isNullOrBlank()) {
return false
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoDone
/**
* Requests a key verification with another user's devices.
*/
@JsonClass(generateAdapter = true)
internal data class KeyVerificationDone(
@Json(name = "transaction_id") override var transactionID: String? = null
) : SendToDeviceObject, VerificationInfoDone {
override fun toSendToDeviceObject() = this
override fun isValid(): Boolean {
if (transactionID.isNullOrBlank()) {
return false
}
return true
}
}

View File

@@ -17,37 +17,35 @@ package im.vector.matrix.android.internal.crypto.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKeyFactory
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoKey
/**
* Sent by both devices to send their ephemeral Curve25519 public key to the other device.
*/
@JsonClass(generateAdapter = true)
data class KeyVerificationKey(
internal data class KeyVerificationKey(
/**
* the ID of the transaction that the message is part of
*/
@Json(name = "transaction_id")
@JvmField
var transactionID: String? = null,
@Json(name = "transaction_id") override val transactionID: String? = null,
/**
* The devices ephemeral public key, as an unpadded base64 string
*/
@JvmField
var key: String? = null
@Json(name = "key") override val key: String? = null
) : SendToDeviceObject {
) : SendToDeviceObject, VerificationInfoKey {
companion object {
fun create(tid: String, key: String): KeyVerificationKey {
return KeyVerificationKey().apply {
this.transactionID = tid
this.key = key
}
companion object : VerificationInfoKeyFactory {
override fun create(tid: String, pubKey: String): KeyVerificationKey {
return KeyVerificationKey(tid, pubKey)
}
}
fun isValid(): Boolean {
override fun toSendToDeviceObject() = this
override fun isValid(): Boolean {
if (transactionID.isNullOrBlank() || key.isNullOrBlank()) {
return false
}

View File

@@ -17,49 +17,32 @@ package im.vector.matrix.android.internal.crypto.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoMac
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoMacFactory
/**
* Sent by both devices to send the MAC of their device key to the other device.
*/
@JsonClass(generateAdapter = true)
data class KeyVerificationMac(
/**
* the ID of the transaction that the message is part of
*/
@Json(name = "transaction_id")
var transactionID: String? = null,
internal data class KeyVerificationMac(
@Json(name = "transaction_id") override val transactionID: String? = null,
@Json(name = "mac") override val mac: Map<String, String>? = null,
@Json(name = "keys") override val keys: String? = null
/**
* A map of key ID to the MAC of the key, as an unpadded base64 string, calculated using the MAC key
*/
@JvmField
var mac: Map<String, String>? = null,
) : SendToDeviceObject, VerificationInfoMac {
/**
* The MAC of the comma-separated, sorted list of key IDs given in the mac property,
* as an unpadded base64 string, calculated using the MAC key.
* For example, if the mac property gives MACs for the keys ed25519:ABCDEFG and ed25519:HIJKLMN, then this property will
* give the MAC of the string “ed25519:ABCDEFG,ed25519:HIJKLMN”.
*/
@JvmField
var keys: String? = null
) : SendToDeviceObject {
fun isValid(): Boolean {
override fun isValid(): Boolean {
if (transactionID.isNullOrBlank() || keys.isNullOrBlank() || mac.isNullOrEmpty()) {
return false
}
return true
}
companion object {
fun create(tid: String, mac: Map<String, String>, keys: String): KeyVerificationMac {
return KeyVerificationMac().apply {
this.transactionID = tid
this.mac = mac
this.keys = keys
}
override fun toSendToDeviceObject(): SendToDeviceObject? = this
companion object : VerificationInfoMacFactory {
override fun create(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac {
return KeyVerificationMac(tid, mac, keys)
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoReady
/**
* Requests a key verification with another user's devices.
*/
@JsonClass(generateAdapter = true)
internal data class KeyVerificationReady(
@Json(name = "from_device") override val fromDevice: String?,
@Json(name = "methods") override val methods: List<String>?,
@Json(name = "transaction_id") override val transactionID: String? = null
) : SendToDeviceObject, VerificationInfoReady {
override fun toSendToDeviceObject() = this
override fun isValid(): Boolean {
return !transactionID.isNullOrBlank() && !fromDevice.isNullOrBlank() && !methods.isNullOrEmpty()
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoRequest
/**
* Requests a key verification with another user's devices.
*/
@JsonClass(generateAdapter = true)
internal data class KeyVerificationRequest(
@Json(name = "from_device") override val fromDevice: String?,
@Json(name = "methods") override val methods: List<String>,
@Json(name = "timestamp") override val timestamp: Long?,
@Json(name = "transaction_id") override var transactionID: String? = null
) : SendToDeviceObject, VerificationInfoRequest {
override fun toSendToDeviceObject() = this
override fun isValid(): Boolean {
if (transactionID.isNullOrBlank() || methods.isNullOrEmpty() || fromDevice.isNullOrEmpty()) {
return false
}
return true
}
}

View File

@@ -18,81 +18,64 @@ package im.vector.matrix.android.internal.crypto.model.rest
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.crypto.sas.SasMode
import im.vector.matrix.android.internal.crypto.verification.SASVerificationTransaction
import im.vector.matrix.android.internal.crypto.verification.SASDefaultVerificationTransaction
import im.vector.matrix.android.internal.crypto.verification.VerificationInfoStart
import im.vector.matrix.android.internal.util.JsonCanonicalizer
import timber.log.Timber
/**
* Sent by Alice to initiate an interactive key verification.
*/
@JsonClass(generateAdapter = true)
class KeyVerificationStart : SendToDeviceObject {
internal data class KeyVerificationStart(
@Json(name = "from_device") override val fromDevice: String? = null,
override val method: String? = null,
@Json(name = "transaction_id") override val transactionID: String? = null,
@Json(name = "key_agreement_protocols") override val keyAgreementProtocols: List<String>? = null,
@Json(name = "hashes") override val hashes: List<String>? = null,
@Json(name = "message_authentication_codes") override val messageAuthenticationCodes: List<String>? = null,
@Json(name = "short_authentication_string") override val shortAuthenticationStrings: List<String>? = null,
// For QR code verification
@Json(name = "secret") override val sharedSecret: String? = null
) : SendToDeviceObject, VerificationInfoStart {
/**
* Alices device ID
*/
@Json(name = "from_device")
var fromDevice: String? = null
var method: String? = null
/**
* String to identify the transaction.
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid.
* Alices device should record this ID and use it in future messages in this transaction.
*/
@Json(name = "transaction_id")
var transactionID: String? = null
/**
* An array of key agreement protocols that Alices client understands.
* Must include “curve25519”.
* Other methods may be defined in the future
*/
@Json(name = "key_agreement_protocols")
var keyAgreementProtocols: List<String>? = null
/**
* An array of hashes that Alices client understands.
* Must include “sha256”. Other methods may be defined in the future.
*/
var hashes: List<String>? = null
/**
* An array of message authentication codes that Alices client understands.
* Must include “hkdf-hmac-sha256”.
* Other methods may be defined in the future.
*/
@Json(name = "message_authentication_codes")
var messageAuthenticationCodes: List<String>? = null
/**
* An array of short authentication string methods that Alices client (and Alice) understands.
* Must include “decimal”.
* This document also describes the “emoji” method.
* Other methods may be defined in the future
*/
@Json(name = "short_authentication_string")
var shortAuthenticationStrings: List<String>? = null
companion object {
const val VERIF_METHOD_SAS = "m.sas.v1"
override fun toCanonicalJson(): String? {
return JsonCanonicalizer.getCanonicalJson(KeyVerificationStart::class.java, this)
}
fun isValid(): Boolean {
// TODO Move those method to the interface?
override fun isValid(): Boolean {
if (transactionID.isNullOrBlank()
|| fromDevice.isNullOrBlank()
|| method != VERIF_METHOD_SAS
|| keyAgreementProtocols.isNullOrEmpty()
|| hashes.isNullOrEmpty()
|| hashes?.contains("sha256") == false
|| messageAuthenticationCodes.isNullOrEmpty()
|| (messageAuthenticationCodes?.contains(SASVerificationTransaction.SAS_MAC_SHA256) == false
&& messageAuthenticationCodes?.contains(SASVerificationTransaction.SAS_MAC_SHA256_LONGKDF) == false)
|| shortAuthenticationStrings.isNullOrEmpty()
|| shortAuthenticationStrings?.contains(SasMode.DECIMAL) == false) {
|| (method == VERIFICATION_METHOD_SAS && !isValidSas())
|| (method == VERIFICATION_METHOD_RECIPROCATE && !isValidReciprocate())) {
Timber.e("## received invalid verification request")
return false
}
return true
}
private fun isValidSas(): Boolean {
if (keyAgreementProtocols.isNullOrEmpty()
|| hashes.isNullOrEmpty()
|| !hashes.contains("sha256") || messageAuthenticationCodes.isNullOrEmpty()
|| (!messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256)
&& !messageAuthenticationCodes.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF))
|| shortAuthenticationStrings.isNullOrEmpty()
|| !shortAuthenticationStrings.contains(SasMode.DECIMAL)) {
return false
}
return true
}
private fun isValidReciprocate(): Boolean {
if (sharedSecret.isNullOrBlank()) {
return false
}
return true
}
override fun toSendToDeviceObject() = this
}

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