Compare commits
357 Commits
develop
...
feature/bc
Author | SHA1 | Date | |
---|---|---|---|
|
8606ac92e1 | ||
|
419673675c | ||
|
8eda089edc | ||
|
6952d17d16 | ||
|
fab0350ca1 | ||
|
54e8debc38 | ||
|
76fa1bfee5 | ||
|
695a2b3345 | ||
|
ee5dfba389 | ||
|
406dfaab85 | ||
|
d337ccd359 | ||
|
7c888f6334 | ||
|
2d388f392f | ||
|
ca2d36303c | ||
|
3146f5209b | ||
|
f07aa9f6f0 | ||
|
3efaa8e171 | ||
|
49239e6bf2 | ||
|
b224a8d626 | ||
|
8555b045e7 | ||
|
71d56108c2 | ||
|
8b7238e051 | ||
|
ee156239b9 | ||
|
f1e8f846b9 | ||
|
3abd68c153 | ||
|
c3f439ea72 | ||
|
f541be4755 | ||
|
d0807b9239 | ||
|
3db82e629b | ||
|
9680b044f9 | ||
|
c52be1f5b1 | ||
|
8c773b6d00 | ||
|
438b456f8e | ||
|
4766bc709d | ||
|
bfe6207a63 | ||
|
2bc0f6c089 | ||
|
14cee226c5 | ||
|
d3ef5cc230 | ||
|
17d25e2597 | ||
|
6965c0c5ab | ||
|
cba3c270f5 | ||
|
0953bc944d | ||
|
adacd55a05 | ||
|
c0614a9fb6 | ||
|
ea37029631 | ||
|
a20fd453d9 | ||
|
03379a6636 | ||
|
ae9711b7d1 | ||
|
2ae4b87f2f | ||
|
b9045eb25f | ||
|
cb4720f6d5 | ||
|
e6444fe9c0 | ||
|
17b8d3c97b | ||
|
b0168dc633 | ||
|
a110c9ee50 | ||
|
43421e3eb9 | ||
|
304989f79c | ||
|
9fbc0cdd46 | ||
|
6e371b7d2d | ||
|
b3d8b1527c | ||
|
dd991e759e | ||
|
501625c19d | ||
|
a9b970832e | ||
|
bb16d77ec6 | ||
|
f8d6511c59 | ||
|
fb1995e9c9 | ||
|
d9342707fd | ||
|
2f3bbab4c4 | ||
|
d302fdc655 | ||
|
5b3e3a7019 | ||
|
4ce6a25c70 | ||
|
bed2c221e3 | ||
|
0c1e439313 | ||
|
5c82bdba38 | ||
|
cf366f7a9c | ||
|
ae02eb18de | ||
|
bda031496a | ||
|
c0f3f394ac | ||
|
87c1d69e26 | ||
|
e5ce77de34 | ||
|
52ed7c019b | ||
|
6dda30e97f | ||
|
b6d73d872b | ||
|
abee136867 | ||
|
327ac2e17b | ||
|
705788394b | ||
|
c1961d1fda | ||
|
e519561edf | ||
|
14ed4692dc | ||
|
022057dd6f | ||
|
eb0faa3484 | ||
|
47bc597b99 | ||
|
797dc9ccbb | ||
|
c253f6b06f | ||
|
3e116ad065 | ||
|
466260bc6a | ||
|
b0aae84727 | ||
|
7c76ba8184 | ||
|
b57dfee77e | ||
|
1809d02541 | ||
|
58a1c80334 | ||
|
a6bc730c32 | ||
|
21ef138e97 | ||
|
42e5dcd50a | ||
|
f559db62b9 | ||
|
a559ebad64 | ||
|
7e49bad411 | ||
|
725e56db08 | ||
|
559404f953 | ||
|
677c879979 | ||
|
a2b3839c46 | ||
|
88733784cd | ||
|
f9f885418a | ||
|
4be50101b3 | ||
|
43f5fa91d4 | ||
|
ae67e51d26 | ||
|
ff17941cee | ||
|
4e6bed87e4 | ||
|
b4bc56ff5c | ||
|
69ede523b6 | ||
|
859d47453c | ||
|
8bd094fa66 | ||
|
309a290cb8 | ||
|
5581b82ab4 | ||
|
39755b08ee | ||
|
48793f531c | ||
|
9cb43ce4c8 | ||
|
ba540eb861 | ||
|
d020d1f6e0 | ||
|
91daa1ab90 | ||
|
ed84e38a9b | ||
|
9c6fccab1d | ||
|
950c7f4a23 | ||
|
046699bc84 | ||
|
0590258d54 | ||
|
e121007d20 | ||
|
7436647571 | ||
|
dc4569db5a | ||
|
2f16a2ebd7 | ||
|
b8637ddaf2 | ||
|
a194213978 | ||
|
ee017b7302 | ||
|
5109f10833 | ||
|
14f974f07f | ||
|
59b2cfa52c | ||
|
dc9f6b866b | ||
|
2e71f38f00 | ||
|
71cc38fa78 | ||
|
8bb2f0584e | ||
|
24dc52e4f6 | ||
|
ee6eec041a | ||
|
69e4b6e8a4 | ||
|
1635c9730a | ||
|
0e44e32d2a | ||
|
210e0241d3 | ||
|
f0f64d8380 | ||
|
2167564812 | ||
|
3f2755b67c | ||
|
d138306b08 | ||
|
46ba0eec9f | ||
|
893e6e3962 | ||
|
fbe098f54b | ||
|
c4bddadebb | ||
|
8bebcc93e7 | ||
|
7fd9ca03be | ||
|
9118b26d2f | ||
|
00280ccb86 | ||
|
3f710ef4c0 | ||
|
38644f0aa2 | ||
|
ac153c80d5 | ||
|
87339fb0ee | ||
|
a5c500cccd | ||
|
c01998ddd3 | ||
|
9e055d9793 | ||
|
f209ae26bc | ||
|
ae635e2b0a | ||
|
50cdbaf041 | ||
|
a3af73261c | ||
|
ba7aa3513b | ||
|
097f05af57 | ||
|
e5af7e6109 | ||
|
7cb143e970 | ||
|
f1da5a1c7c | ||
|
50268540c3 | ||
|
d6ecc7d330 | ||
|
5c7b248ed2 | ||
|
f9476f12af | ||
|
2b8783b489 | ||
|
3b93d6b08c | ||
|
021041fc2e | ||
|
406fd0d8d5 | ||
|
d3a761a73a | ||
|
28d4573124 | ||
|
c266842da9 | ||
|
504fd95b26 | ||
|
995f1973c7 | ||
|
d0502c4f6b | ||
|
f1da77fb6b | ||
|
00d1233512 | ||
|
3365c10fe3 | ||
|
c85847df57 | ||
|
b012a0ff75 | ||
|
e2006f9dc6 | ||
|
c551b9e0bb | ||
|
2fc691eed2 | ||
|
813b48df6a | ||
|
52dd4bc454 | ||
|
99ff097fc3 | ||
|
3993d2d4f2 | ||
|
3fa9fc5b7b | ||
|
cbed5be810 | ||
|
38ce3ebed7 | ||
|
8089e972a5 | ||
|
93615ddba9 | ||
|
b500364322 | ||
|
93f36db43c | ||
|
2097f4e6c2 | ||
|
eae2a51a2d | ||
|
b33537fd6e | ||
|
7650e43362 | ||
|
b26aba9fc0 | ||
|
f609bfaf10 | ||
|
f8ad024f1b | ||
|
54c3b4192e | ||
|
d4090c4b0a | ||
|
80e80e07b3 | ||
|
33c2184c52 | ||
|
d24c94d0f9 | ||
|
85e4b5eb49 | ||
|
7e49760da0 | ||
|
cd5aad9a31 | ||
|
2c1dc053ed | ||
|
bcfb121215 | ||
|
304c89a56d | ||
|
53b3f54808 | ||
|
03499b5309 | ||
|
6bb7d5faaa | ||
|
1f7311a428 | ||
|
05119bcf90 | ||
|
02b8b1f5b1 | ||
|
d21137d910 | ||
|
4473af85b1 | ||
|
6523ca5afe | ||
|
d15269a4bd | ||
|
846242217b | ||
|
f95c4ae088 | ||
|
f854e9cf1c | ||
|
b53b0a0093 | ||
|
948aa1a141 | ||
|
aad18ebec7 | ||
|
6a79d022c3 | ||
|
6649aaca2e | ||
|
e97ce33ed9 | ||
|
d00b54929f | ||
|
0cb9f6be10 | ||
|
a4e1a5bbcb | ||
|
e46578a087 | ||
|
c0bac69733 | ||
|
5ad596c3bc | ||
|
a144b1f7b5 | ||
|
f110bf34fa | ||
|
688c167166 | ||
|
49fa34e997 | ||
|
fe4abbbeef | ||
|
324cdc4db1 | ||
|
326641a7e5 | ||
|
711e607fca | ||
|
389273d56a | ||
|
09c0ca10e5 | ||
|
ed902fc42a | ||
|
c5173dde71 | ||
|
8bfb7a6e0c | ||
|
0db07011b1 | ||
|
91d28658fc | ||
|
0afdcb35f1 | ||
|
2805772d0a | ||
|
3ba29b4ea9 | ||
|
5b761ef7d1 | ||
|
aebfef8fa9 | ||
|
7d67c79d29 | ||
|
543a638e87 | ||
|
0d708bc35a | ||
|
e9e3d129ba | ||
|
99477914df | ||
|
9296cab4fc | ||
|
188d2d57c0 | ||
|
74a1c226a4 | ||
|
8692f05e34 | ||
|
427eb5e249 | ||
|
5253f9708c | ||
|
edfd1b2fe0 | ||
|
08d0787cc9 | ||
|
6d05f5b993 | ||
|
533895cb38 | ||
|
336697a38c | ||
|
182fc84186 | ||
|
0b064f647a | ||
|
ef93d9e625 | ||
|
dc8711be30 | ||
|
10c7f5b989 | ||
|
6af8041fb4 | ||
|
9d5ef01ce0 | ||
|
5533c2acae | ||
|
57bb723bac | ||
|
7f89e33037 | ||
|
32cf645c5f | ||
|
d49bdbe016 | ||
|
32c1fd9c85 | ||
|
1bff219197 | ||
|
dace959d69 | ||
|
3812162f4f | ||
|
4b157f7915 | ||
|
4eeb47dc56 | ||
|
6bc825b0bc | ||
|
36451e5410 | ||
|
6e53ab2bcf | ||
|
629623f720 | ||
|
555d24fea5 | ||
|
1773a361d1 | ||
|
758e8f7fb6 | ||
|
67f238069a | ||
|
5b2629ba00 | ||
|
981e6b65b0 | ||
|
515c9be2d9 | ||
|
f5348d6c9d | ||
|
e4ac5f6c13 | ||
|
669a5f9815 | ||
|
7f86f512ed | ||
|
ab8d365c10 | ||
|
c97e384790 | ||
|
5f848093b9 | ||
|
4c44a5e108 | ||
|
da35c9b6bd | ||
|
c8c7f23298 | ||
|
c828326755 | ||
|
c33a4710fe | ||
|
891622d64b | ||
|
3b73adf3c5 | ||
|
8b1b771ae6 | ||
|
f6d31f15f1 | ||
|
e2692ec604 | ||
|
01149c8d45 | ||
|
930e6f4e9b | ||
|
504e1e31bd | ||
|
2d620e2ddf | ||
|
75838fda2a | ||
|
d50df9537c | ||
|
4589b882c0 | ||
|
3ddbe7e69b | ||
|
1eeb97ec51 | ||
|
e16c5d07e5 | ||
|
f01e2460e1 | ||
|
628f530633 | ||
|
0b9be11d85 | ||
|
a557c05890 | ||
|
5886dc1cbc | ||
|
de5a02b02a |
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1,2 +1,3 @@
|
|||||||
**/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text
|
**/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
**/src/androidTest/assets/*.realm filter=lfs diff=lfs merge=lfs -text
|
**/src/androidTest/assets/*.realm filter=lfs diff=lfs merge=lfs -text
|
||||||
|
**/matrix-rust-sdk-crypto.aar filter=lfs diff=lfs merge=lfs -text
|
||||||
|
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -34,7 +34,7 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-gradle-
|
${{ runner.os }}-gradle-
|
||||||
- name: Assemble ${{ matrix.target }} debug apk
|
- name: Assemble ${{ matrix.target }} debug apk
|
||||||
run: ./gradlew assemble${{ matrix.target }}Debug $CI_GRADLE_ARG_PROPERTIES
|
run: ./gradlew assemble${{ matrix.target }}KotlinCryptoDebug $CI_GRADLE_ARG_PROPERTIES
|
||||||
- name: Upload ${{ matrix.target }} debug APKs
|
- name: Upload ${{ matrix.target }} debug APKs
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
@ -59,7 +59,7 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-gradle-
|
${{ runner.os }}-gradle-
|
||||||
- name: Assemble GPlay unsigned apk
|
- name: Assemble GPlay unsigned apk
|
||||||
run: ./gradlew clean assembleGplayRelease $CI_GRADLE_ARG_PROPERTIES
|
run: ./gradlew clean assembleGplayKotlinCryptoRelease $CI_GRADLE_ARG_PROPERTIES
|
||||||
- name: Upload Gplay unsigned APKs
|
- name: Upload Gplay unsigned APKs
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
@ -81,7 +81,7 @@ jobs:
|
|||||||
- name: Execute exodus-standalone
|
- name: Execute exodus-standalone
|
||||||
uses: docker://exodusprivacy/exodus-standalone:latest
|
uses: docker://exodusprivacy/exodus-standalone:latest
|
||||||
with:
|
with:
|
||||||
args: /github/workspace/gplay/release/vector-gplay-universal-release-unsigned.apk -j -o /github/workspace/exodus.json
|
args: /github/workspace/gplayKotlinCrypto/release/vector-gplay-kotlinCrypto-universal-release-unsigned.apk -j -o /github/workspace/exodus.json
|
||||||
- name: Upload exodus json report
|
- name: Upload exodus json report
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
37
.github/workflows/elementr.yml
vendored
Normal file
37
.github/workflows/elementr.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
name: ER APK Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request: { }
|
||||||
|
push:
|
||||||
|
branches: [ develop ]
|
||||||
|
|
||||||
|
# Enrich gradle.properties for CI/CD
|
||||||
|
env:
|
||||||
|
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
|
||||||
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
debug:
|
||||||
|
name: Build debug APKs ER
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.ref != 'refs/heads/main'
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
target: [ Gplay, Fdroid ]
|
||||||
|
# Allow all jobs on develop. Just one per PR.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.ref == 'refs/heads/develop' && format('elementr-{0}-{1}', matrix.target, github.sha) || format('build-er-debug-{0}-{1}', matrix.target, github.ref) }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.gradle/caches
|
||||||
|
~/.gradle/wrapper
|
||||||
|
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-gradle-
|
||||||
|
- name: Assemble ${{ matrix.target }} debug apk
|
||||||
|
run: ./gradlew assemble${{ matrix.target }}RustCryptoDebug $CI_GRADLE_ARG_PROPERTIES
|
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
@ -38,7 +38,7 @@ jobs:
|
|||||||
yes n | towncrier build --version nightly
|
yes n | towncrier build --version nightly
|
||||||
- name: Build and upload Gplay Nightly APK
|
- name: Build and upload Gplay Nightly APK
|
||||||
run: |
|
run: |
|
||||||
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES
|
./gradlew assembleGplayKotlinCryptoNightly appDistributionUploadGplayKotlinCryptoNightly $CI_GRADLE_ARG_PROPERTIES
|
||||||
env:
|
env:
|
||||||
ELEMENT_ANDROID_NIGHTLY_KEYID: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYID }}
|
ELEMENT_ANDROID_NIGHTLY_KEYID: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYID }}
|
||||||
ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD }}
|
ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD }}
|
||||||
|
46
.github/workflows/nightly_er.yml
vendored
Normal file
46
.github/workflows/nightly_er.yml
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
name: Build and release Element R nightly APK
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
# Every nights at 4
|
||||||
|
- cron: "0 4 * * *"
|
||||||
|
|
||||||
|
env:
|
||||||
|
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
|
||||||
|
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
nightly:
|
||||||
|
name: Build and publish ER nightly Gplay APK to Firebase
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Set up Python 3.8
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: 3.8
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.gradle/caches
|
||||||
|
~/.gradle/wrapper
|
||||||
|
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-gradle-
|
||||||
|
- name: Install towncrier
|
||||||
|
run: |
|
||||||
|
python3 -m pip install towncrier
|
||||||
|
- name: Prepare changelog file
|
||||||
|
run: |
|
||||||
|
mv towncrier.toml towncrier.toml.bak
|
||||||
|
sed 's/CHANGES\.md/CHANGES_NIGHTLY\.md/' towncrier.toml.bak > towncrier.toml
|
||||||
|
rm towncrier.toml.bak
|
||||||
|
yes n | towncrier build --version nightly
|
||||||
|
- name: Build and upload Gplay Nightly ER APK
|
||||||
|
run: |
|
||||||
|
./gradlew assembleGplayRustCryptoNightly appDistributionUploadGplayRustCryptoNightly $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
env:
|
||||||
|
ELEMENT_ANDROID_NIGHTLY_KEYID: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYID }}
|
||||||
|
ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD }}
|
||||||
|
ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD }}
|
||||||
|
FIREBASE_TOKEN: ${{ secrets.ELEMENT_R_NIGHTLY_FIREBASE_TOKEN }}
|
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -71,7 +71,7 @@ jobs:
|
|||||||
disable-animations: true
|
disable-animations: true
|
||||||
# emulator-build: 7425822
|
# emulator-build: 7425822
|
||||||
script: |
|
script: |
|
||||||
./gradlew gatherGplayDebugStringTemplates $CI_GRADLE_ARG_PROPERTIES
|
./gradlew gatherGplayKotlinCryptoDebugStringTemplates $CI_GRADLE_ARG_PROPERTIES
|
||||||
./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
|
./gradlew unitTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
|
||||||
./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
|
./gradlew instrumentationTestsWithCoverage $CI_GRADLE_ARG_PROPERTIES
|
||||||
./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES
|
./gradlew generateCoverageReport $CI_GRADLE_ARG_PROPERTIES
|
||||||
|
10
.gitignore
vendored
10
.gitignore
vendored
@ -11,6 +11,9 @@
|
|||||||
/benchmark-out
|
/benchmark-out
|
||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
rust-sdk/target/*
|
||||||
|
rust-sdk/src/uniffi/*
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
/tmp
|
/tmp
|
||||||
/fastlane/private
|
/fastlane/private
|
||||||
@ -23,3 +26,10 @@
|
|||||||
/yarn.lock
|
/yarn.lock
|
||||||
/node_modules
|
/node_modules
|
||||||
**/out/failures
|
**/out/failures
|
||||||
|
|
||||||
|
# For manual dependency to rust crypto sdk
|
||||||
|
matrix-sdk-android/src/main/jniLibs/
|
||||||
|
|
||||||
|
matrix-sdk-android/libs/crypto-android-release.aar
|
||||||
|
|
||||||
|
matrix-sdk-android/libs/matrix-rust-sdk-crypto.aar
|
||||||
|
11
build.gradle
11
build.gradle
@ -121,6 +121,15 @@ allprojects {
|
|||||||
groups.jcenter.group.each { includeGroup it }
|
groups.jcenter.group.each { includeGroup it }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maven {
|
||||||
|
url 'https://s01.oss.sonatype.org/content/repositories/snapshots'
|
||||||
|
content {
|
||||||
|
groups.mavenSnapshots.regex.each { includeGroupByRegex it }
|
||||||
|
groups.mavenSnapshots.group.each { includeGroup it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||||
@ -314,7 +323,7 @@ tasks.register("recordScreenshots", GradleBuild) {
|
|||||||
|
|
||||||
tasks.register("verifyScreenshots", GradleBuild) {
|
tasks.register("verifyScreenshots", GradleBuild) {
|
||||||
startParameter.projectProperties.screenshot = ""
|
startParameter.projectProperties.screenshot = ""
|
||||||
tasks = [':vector:verifyPaparazziDebug']
|
tasks = [':vector:verifyPaparazziKotlinCryptoDebug']
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.initScreenshotTests = { project ->
|
ext.initScreenshotTests = { project ->
|
||||||
|
@ -87,5 +87,5 @@ task unitTestsWithCoverage(type: GradleBuild) {
|
|||||||
task instrumentationTestsWithCoverage(type: GradleBuild) {
|
task instrumentationTestsWithCoverage(type: GradleBuild) {
|
||||||
startParameter.projectProperties.coverage = [enableTestCoverage: true]
|
startParameter.projectProperties.coverage = [enableTestCoverage: true]
|
||||||
startParameter.projectProperties['android.testInstrumentationRunnerArguments.notPackage'] = 'im.vector.app.ui'
|
startParameter.projectProperties['android.testInstrumentationRunnerArguments.notPackage'] = 'im.vector.app.ui'
|
||||||
tasks = [':vector-app:connectedGplayDebugAndroidTest', ':vector:connectedDebugAndroidTest', 'matrix-sdk-android:connectedDebugAndroidTest']
|
tasks = [':vector-app:connectedGplayKotlinCryptoDebugAndroidTest', ':vector:connectedKotlinCryptoDebugAndroidTest', 'matrix-sdk-android:connectedKotlinCryptoDebugAndroidTest']
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
ext.groups = [
|
ext.groups = [
|
||||||
jitpack : [
|
jitpack : [
|
||||||
regex: [
|
regex: [
|
||||||
],
|
],
|
||||||
group: [
|
group: [
|
||||||
@ -15,7 +15,7 @@ ext.groups = [
|
|||||||
'com.github.Zhuinden',
|
'com.github.Zhuinden',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
jitsi : [
|
jitsi : [
|
||||||
regex: [
|
regex: [
|
||||||
],
|
],
|
||||||
group: [
|
group: [
|
||||||
@ -24,7 +24,7 @@ ext.groups = [
|
|||||||
'org.webkit',
|
'org.webkit',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
google : [
|
google : [
|
||||||
regex: [
|
regex: [
|
||||||
'androidx\\..*',
|
'androidx\\..*',
|
||||||
'com\\.android\\.tools\\..*',
|
'com\\.android\\.tools\\..*',
|
||||||
@ -45,6 +45,13 @@ ext.groups = [
|
|||||||
'com.vanniktech',
|
'com.vanniktech',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
mavenSnapshots: [
|
||||||
|
regex: [
|
||||||
|
],
|
||||||
|
group: [
|
||||||
|
'org.matrix.rustcomponents'
|
||||||
|
]
|
||||||
|
],
|
||||||
mavenCentral: [
|
mavenCentral: [
|
||||||
regex: [
|
regex: [
|
||||||
],
|
],
|
||||||
@ -205,6 +212,7 @@ ext.groups = [
|
|||||||
'org.jvnet.staxex',
|
'org.jvnet.staxex',
|
||||||
'org.maplibre.gl',
|
'org.maplibre.gl',
|
||||||
'org.matrix.android',
|
'org.matrix.android',
|
||||||
|
'org.matrix.rustcomponents',
|
||||||
'org.mockito',
|
'org.mockito',
|
||||||
'org.mongodb',
|
'org.mongodb',
|
||||||
'org.objenesis',
|
'org.objenesis',
|
||||||
@ -224,7 +232,7 @@ ext.groups = [
|
|||||||
'xml-apis',
|
'xml-apis',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
jcenter : [
|
jcenter : [
|
||||||
regex: [
|
regex: [
|
||||||
],
|
],
|
||||||
group: [
|
group: [
|
||||||
|
@ -48,7 +48,7 @@ mv towncrier.toml towncrier.toml.bak
|
|||||||
sed 's/CHANGES\.md/CHANGES_NIGHTLY\.md/' towncrier.toml.bak > towncrier.toml
|
sed 's/CHANGES\.md/CHANGES_NIGHTLY\.md/' towncrier.toml.bak > towncrier.toml
|
||||||
rm towncrier.toml.bak
|
rm towncrier.toml.bak
|
||||||
yes n | towncrier build --version nightly
|
yes n | towncrier build --version nightly
|
||||||
./gradlew assembleGplayNightly appDistributionUploadGplayNightly $CI_GRADLE_ARG_PROPERTIES
|
./gradlew assembleGplayKotlinCryptoNightly appDistributionUploadGplayKotlinCryptoNightly $CI_GRADLE_ARG_PROPERTIES
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you can reset the change on the codebase.
|
Then you can reset the change on the codebase.
|
||||||
|
20
flavor.gradle
Normal file
20
flavor.gradle
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
android {
|
||||||
|
|
||||||
|
flavorDimensions "crypto"
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
kotlinCrypto {
|
||||||
|
dimension "crypto"
|
||||||
|
isDefault = true
|
||||||
|
// versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}"
|
||||||
|
// buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"JC\""
|
||||||
|
// buildConfigField "String", "FLAVOR_DESCRIPTION", "\"KotlinCrypto\""
|
||||||
|
}
|
||||||
|
rustCrypto {
|
||||||
|
dimension "crypto"
|
||||||
|
// // versionName "${versionMajor}.${versionMinor}.${versionPatch}${getFdroidVersionSuffix()}"
|
||||||
|
// buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"RC\""
|
||||||
|
// buildConfigField "String", "FLAVOR_DESCRIPTION", "\"RustCrypto\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2
library/rustCrypto/build.gradle
Normal file
2
library/rustCrypto/build.gradle
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
configurations.maybeCreate("default")
|
||||||
|
artifacts.add("default", file('matrix-rust-sdk-crypto.aar'))
|
3
library/rustCrypto/matrix-rust-sdk-crypto.aar
Normal file
3
library/rustCrypto/matrix-rust-sdk-crypto.aar
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:3f303e8830bb4bd7005b2a166118d7771ed07259822ebb6f888abb0ed459f0cc
|
||||||
|
size 50458247
|
@ -414,6 +414,7 @@
|
|||||||
<string name="action_play">Play</string>
|
<string name="action_play">Play</string>
|
||||||
<string name="action_dismiss">Dismiss</string>
|
<string name="action_dismiss">Dismiss</string>
|
||||||
<string name="action_reset">Reset</string>
|
<string name="action_reset">Reset</string>
|
||||||
|
<string name="action_proceed_to_reset">Proceed to reset</string>
|
||||||
<string name="action_learn_more">Learn more</string>
|
<string name="action_learn_more">Learn more</string>
|
||||||
<string name="action_next">Next</string>
|
<string name="action_next">Next</string>
|
||||||
<string name="action_got_it">Got it</string>
|
<string name="action_got_it">Got it</string>
|
||||||
@ -2317,6 +2318,7 @@
|
|||||||
<string name="verification_verify_user">Verify %s</string>
|
<string name="verification_verify_user">Verify %s</string>
|
||||||
<string name="verification_verified_user">Verified %s</string>
|
<string name="verification_verified_user">Verified %s</string>
|
||||||
<string name="verification_request_waiting_for">Waiting for %s…</string>
|
<string name="verification_request_waiting_for">Waiting for %s…</string>
|
||||||
|
<string name="verification_request_waiting_for_recovery">Verifying from Secure Key or Phrase…</string>
|
||||||
<string name="room_profile_not_encrypted_subtitle">Messages in this room are not end-to-end encrypted.</string>
|
<string name="room_profile_not_encrypted_subtitle">Messages in this room are not end-to-end encrypted.</string>
|
||||||
<string name="direct_room_profile_not_encrypted_subtitle">Messages here are not end-to-end encrypted.</string>
|
<string name="direct_room_profile_not_encrypted_subtitle">Messages here are not end-to-end encrypted.</string>
|
||||||
<string name="room_profile_encrypted_subtitle">Messages in this room are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them.</string>
|
<string name="room_profile_encrypted_subtitle">Messages in this room are end-to-end encrypted.\n\nYour messages are secured with locks and only you and the recipient have the unique keys to unlock them.</string>
|
||||||
@ -2426,6 +2428,8 @@
|
|||||||
<string name="crosssigning_cannot_verify_this_session">Unable to verify this device</string>
|
<string name="crosssigning_cannot_verify_this_session">Unable to verify this device</string>
|
||||||
<string name="crosssigning_cannot_verify_this_session_desc">You won’t be able to access encrypted message history. Reset your Secure Message Backup and verification keys to start fresh.</string>
|
<string name="crosssigning_cannot_verify_this_session_desc">You won’t be able to access encrypted message history. Reset your Secure Message Backup and verification keys to start fresh.</string>
|
||||||
|
|
||||||
|
<string name="verification_verify_with_another_device">Verify with another device</string>
|
||||||
|
<string name="verification_verify_identity">Verify your identity to access encrypted messages and prove your identity to others.</string>
|
||||||
<string name="verification_open_other_to_verify">Use an existing session to verify this one, granting it access to encrypted messages.</string>
|
<string name="verification_open_other_to_verify">Use an existing session to verify this one, granting it access to encrypted messages.</string>
|
||||||
|
|
||||||
<string name="verification_profile_verify">Verify</string>
|
<string name="verification_profile_verify">Verify</string>
|
||||||
@ -2500,6 +2504,7 @@
|
|||||||
|
|
||||||
<string name="new_session">New login. Was this you?</string>
|
<string name="new_session">New login. Was this you?</string>
|
||||||
<string name="verify_new_session_notice">Use this session to verify your new one, granting it access to encrypted messages.</string>
|
<string name="verify_new_session_notice">Use this session to verify your new one, granting it access to encrypted messages.</string>
|
||||||
|
<string name="verification_request_was_sent">A verification request has been sent. Open one of your other sessions to accept and start the verification.</string>
|
||||||
<string name="verify_new_session_was_not_me">This wasn’t me</string>
|
<string name="verify_new_session_was_not_me">This wasn’t me</string>
|
||||||
<string name="verify_new_session_compromized">Your account may be compromised</string>
|
<string name="verify_new_session_compromized">Your account may be compromised</string>
|
||||||
|
|
||||||
@ -2639,8 +2644,10 @@
|
|||||||
<string name="bad_passphrase_key_reset_all_action">Forgot or lost all recovery options? Reset everything</string>
|
<string name="bad_passphrase_key_reset_all_action">Forgot or lost all recovery options? Reset everything</string>
|
||||||
<string name="secure_backup_reset_all">Reset everything</string>
|
<string name="secure_backup_reset_all">Reset everything</string>
|
||||||
<string name="secure_backup_reset_all_no_other_devices">Only do this if you have no other device you can verify this device with.</string>
|
<string name="secure_backup_reset_all_no_other_devices">Only do this if you have no other device you can verify this device with.</string>
|
||||||
|
<string name="secure_backup_reset_all_no_other_devices_long">Resetting your verification keys cannot be undone. After resetting, you won\'t have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.</string>
|
||||||
<string name="secure_backup_reset_if_you_reset_all">If you reset everything</string>
|
<string name="secure_backup_reset_if_you_reset_all">If you reset everything</string>
|
||||||
<string name="secure_backup_reset_no_history">You will restart with no history, no messages, trusted devices or trusted users</string>
|
<string name="secure_backup_reset_no_history">You will restart with no history, no messages, trusted devices or trusted users</string>
|
||||||
|
<string name="secure_backup_reset_danger_warning">Please only proceed if you\'re sure you\'ve lost all of your other devices and your security key.</string>
|
||||||
<plurals name="secure_backup_reset_devices_you_can_verify">
|
<plurals name="secure_backup_reset_devices_you_can_verify">
|
||||||
<item quantity="one">Show the device you can verify with now</item>
|
<item quantity="one">Show the device you can verify with now</item>
|
||||||
<item quantity="other">Show %d devices you can verify with now</item>
|
<item quantity="other">Show %d devices you can verify with now</item>
|
||||||
@ -2655,6 +2662,7 @@
|
|||||||
|
|
||||||
<string name="unencrypted">Unencrypted</string>
|
<string name="unencrypted">Unencrypted</string>
|
||||||
<string name="encrypted_unverified">Encrypted by an unverified device</string>
|
<string name="encrypted_unverified">Encrypted by an unverified device</string>
|
||||||
|
<string name="encrypted_by_deleted">Encrypted by a deleted device</string>
|
||||||
<string name="key_authenticity_not_guaranteed">The authenticity of this encrypted message can\'t be guaranteed on this device.</string>
|
<string name="key_authenticity_not_guaranteed">The authenticity of this encrypted message can\'t be guaranteed on this device.</string>
|
||||||
<!-- TODO TO BE REMOVED -->
|
<!-- TODO TO BE REMOVED -->
|
||||||
<string name="review_logins" tools:ignore="UnusedResources">Review where you’re logged in</string>
|
<string name="review_logins" tools:ignore="UnusedResources">Review where you’re logged in</string>
|
||||||
|
@ -3,6 +3,7 @@ plugins {
|
|||||||
id 'com.android.library'
|
id 'com.android.library'
|
||||||
id 'org.jetbrains.kotlin.android'
|
id 'org.jetbrains.kotlin.android'
|
||||||
}
|
}
|
||||||
|
apply from: '../flavor.gradle'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace "org.matrix.android.sdk.flow"
|
namespace "org.matrix.android.sdk.flow"
|
||||||
@ -30,11 +31,23 @@ android {
|
|||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "11"
|
jvmTarget = "11"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// publishNonDefault true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//configurations {
|
||||||
|
// kotlinCryptoDebugImplementation
|
||||||
|
// kotlinCryptoReleaseImplementation
|
||||||
|
// rustCryptoDebugImplementation
|
||||||
|
// rustCryptoReleaseImplementation
|
||||||
|
//}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(":matrix-sdk-android")
|
implementation project(":matrix-sdk-android")
|
||||||
|
// kotlinCryptoDebugImplementation project(path: ":matrix-sdk-android", configuration :"kotlinCryptoDebug")
|
||||||
|
// kotlinCryptoReleaseImplementation project(path: ":matrix-sdk-android", configuration :"kotlinCryptoRelease")
|
||||||
|
// rustCryptoDebugImplementation project(path: ":matrix-sdk-android", configuration :"rustCryptoDebug")
|
||||||
|
// rustCryptoReleaseImplementation project(path: ":matrix-sdk-android", configuration :"rustCryptoDebug")
|
||||||
implementation libs.jetbrains.coroutinesCore
|
implementation libs.jetbrains.coroutinesCore
|
||||||
implementation libs.jetbrains.coroutinesAndroid
|
implementation libs.jetbrains.coroutinesAndroid
|
||||||
implementation libs.androidx.lifecycleLivedata
|
implementation libs.androidx.lifecycleLivedata
|
||||||
|
@ -41,6 +41,7 @@ dokkaHtml {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
apply from: '../flavor.gradle'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace "org.matrix.android.sdk"
|
namespace "org.matrix.android.sdk"
|
||||||
@ -75,7 +76,7 @@ android {
|
|||||||
|
|
||||||
testOptions {
|
testOptions {
|
||||||
// Comment to run on Android 12
|
// Comment to run on Android 12
|
||||||
// execution 'ANDROIDX_TEST_ORCHESTRATOR'
|
execution 'ANDROIDX_TEST_ORCHESTRATOR'
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@ -124,6 +125,7 @@ android {
|
|||||||
java.srcDirs += "src/sharedTest/java"
|
java.srcDirs += "src/sharedTest/java"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static def gitRevision() {
|
static def gitRevision() {
|
||||||
@ -141,12 +143,23 @@ static def gitRevisionDate() {
|
|||||||
return cmd.execute().text.trim()
|
return cmd.execute().text.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configurations.all {
|
||||||
|
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation libs.jetbrains.coroutinesCore
|
implementation libs.jetbrains.coroutinesCore
|
||||||
implementation libs.jetbrains.coroutinesAndroid
|
implementation libs.jetbrains.coroutinesAndroid
|
||||||
|
|
||||||
|
|
||||||
|
// implementation(name: 'crypto-android-release', ext: 'aar')
|
||||||
|
implementation 'net.java.dev.jna:jna:5.10.0@aar'
|
||||||
|
|
||||||
|
// implementation libs.androidx.appCompat
|
||||||
implementation libs.androidx.core
|
implementation libs.androidx.core
|
||||||
|
|
||||||
|
rustCryptoImplementation libs.androidx.lifecycleLivedata
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
implementation libs.androidx.lifecycleCommon
|
implementation libs.androidx.lifecycleCommon
|
||||||
implementation libs.androidx.lifecycleProcess
|
implementation libs.androidx.lifecycleProcess
|
||||||
@ -203,6 +216,10 @@ dependencies {
|
|||||||
|
|
||||||
implementation libs.google.phonenumber
|
implementation libs.google.phonenumber
|
||||||
|
|
||||||
|
// rustCryptoImplementation 'org.matrix.rustcomponents:crypto-android:0.2.1-SNAPSHOT'
|
||||||
|
// rustCryptoImplementation files('libs/matrix-rust-sdk-crypto.aar')
|
||||||
|
rustCryptoApi project(":library:rustCrypto")
|
||||||
|
|
||||||
testImplementation libs.tests.junit
|
testImplementation libs.tests.junit
|
||||||
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
||||||
testImplementation libs.mockk.mockk
|
testImplementation libs.mockk.mockk
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:59b4957aa2f9cdc17b14ec8546e144537fac9dee050c6eb173f56fa8602c2736
|
||||||
|
size 2097152
|
@ -19,13 +19,12 @@ package org.matrix.android.sdk
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.matrix.android.sdk.common.RetryTestRule
|
|
||||||
import org.matrix.android.sdk.test.shared.createTimberTestRule
|
import org.matrix.android.sdk.test.shared.createTimberTestRule
|
||||||
|
|
||||||
interface InstrumentedTest {
|
interface InstrumentedTest {
|
||||||
|
|
||||||
@Rule
|
// @Rule
|
||||||
fun retryTestRule() = RetryTestRule(3)
|
// fun retryTestRule() = RetryTestRule(3)
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
fun timberTestRule() = createTimberTestRule()
|
fun timberTestRule() = createTimberTestRule()
|
||||||
|
@ -85,13 +85,15 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig:
|
|||||||
internal fun runCryptoTest(context: Context, cryptoConfig: MXCryptoConfig? = null, autoSignoutOnClose: Boolean = true, block: suspend CoroutineScope.(CryptoTestHelper, CommonTestHelper) -> Unit) {
|
internal fun runCryptoTest(context: Context, cryptoConfig: MXCryptoConfig? = null, autoSignoutOnClose: Boolean = true, block: suspend CoroutineScope.(CryptoTestHelper, CommonTestHelper) -> Unit) {
|
||||||
val testHelper = CommonTestHelper(context, cryptoConfig)
|
val testHelper = CommonTestHelper(context, cryptoConfig)
|
||||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||||
return runTest(dispatchTimeoutMs = TestConstants.timeOutMillis) {
|
return try {
|
||||||
try {
|
runTest(dispatchTimeoutMs = TestConstants.timeOutMillis * 2) {
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.Main) {
|
||||||
block(cryptoTestHelper, testHelper)
|
block(cryptoTestHelper, testHelper)
|
||||||
}
|
}
|
||||||
} finally {
|
}
|
||||||
if (autoSignoutOnClose) {
|
} finally {
|
||||||
|
if (autoSignoutOnClose) {
|
||||||
|
runBlocking {
|
||||||
testHelper.cleanUpOpenedSessions()
|
testHelper.cleanUpOpenedSessions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,7 +252,7 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig:
|
|||||||
|
|
||||||
// not sure why it's taking so long :/
|
// not sure why it's taking so long :/
|
||||||
wrapWithTimeout(90_000) {
|
wrapWithTimeout(90_000) {
|
||||||
Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $roomID")
|
Log.v("#E2E TEST", "${otherSession.myUserId.take(10)} tries to join room $roomID")
|
||||||
try {
|
try {
|
||||||
otherSession.roomService().joinRoom(roomID)
|
otherSession.roomService().joinRoom(roomID)
|
||||||
} catch (ex: JoinRoomFailure.JoinedWithTimeout) {
|
} catch (ex: JoinRoomFailure.JoinedWithTimeout) {
|
||||||
@ -432,6 +434,31 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val backoff = listOf(500L, 1_000L, 1_000L, 3_000L, 3_000L, 5_000L)
|
||||||
|
suspend fun retryWithBackoff(
|
||||||
|
timeout: Long = TestConstants.timeOutMillis,
|
||||||
|
// we use on fail to let caller report a proper error that will show nicely in junit test result with correct line
|
||||||
|
// just call fail with your message
|
||||||
|
onFail: (() -> Unit)? = null,
|
||||||
|
predicate: suspend () -> Boolean,
|
||||||
|
) {
|
||||||
|
var backoffTry = 0
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
while (!predicate()) {
|
||||||
|
Timber.v("## retryWithBackoff Trial nb $backoffTry")
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
delay(backoff[backoffTry.coerceAtMost(backoff.size - 1)])
|
||||||
|
}
|
||||||
|
backoffTry++
|
||||||
|
if (System.currentTimeMillis() - now > timeout) {
|
||||||
|
Timber.v("## retryWithBackoff Trial fail")
|
||||||
|
onFail?.invoke()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Timber.w("## VALR Trial success for")
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun <T> waitForCallback(timeout: Long = TestConstants.timeOutMillis, block: (MatrixCallback<T>) -> Unit): T {
|
suspend fun <T> waitForCallback(timeout: Long = TestConstants.timeOutMillis, block: (MatrixCallback<T>) -> Unit): T {
|
||||||
return wrapWithTimeout(timeout) {
|
return wrapWithTimeout(timeout) {
|
||||||
suspendCoroutine { continuation ->
|
suspendCoroutine { continuation ->
|
||||||
|
@ -37,4 +37,10 @@ data class CryptoTestData(
|
|||||||
testHelper.signOutAndClose(it)
|
testHelper.signOutAndClose(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun initializeCrossSigning(testHelper: CryptoTestHelper) {
|
||||||
|
sessions.forEach {
|
||||||
|
testHelper.initializeCrossSigning(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,18 +33,18 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S
|
|||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupUtils
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.getRoomSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
@ -52,7 +52,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
|||||||
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
|
||||||
import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner
|
import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner
|
||||||
import org.matrix.android.sdk.api.session.securestorage.KeyRef
|
import org.matrix.android.sdk.api.session.securestorage.KeyRef
|
||||||
import org.matrix.android.sdk.api.util.toBase64NoPadding
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.coroutines.Continuation
|
import kotlin.coroutines.Continuation
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
@ -121,6 +121,80 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||||||
return CryptoTestData(aliceRoomId, listOf(aliceSession, bobSession))
|
return CryptoTestData(aliceRoomId, listOf(aliceSession, bobSession))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun inviteNewUsersAndWaitForThemToJoin(session: Session, roomId: String, usernames: List<String>): List<Session> {
|
||||||
|
val newSessions = usernames.map { username ->
|
||||||
|
testHelper.createAccount(username, SessionTestParams(true)).also {
|
||||||
|
it.cryptoService().enableKeyGossiping(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val room = session.getRoom(roomId)!!
|
||||||
|
|
||||||
|
Log.v("#E2E TEST", "accounts for ${usernames.joinToString(",") { it.take(10) }} created")
|
||||||
|
// we want to invite them in the room
|
||||||
|
newSessions.forEach { newSession ->
|
||||||
|
Log.v("#E2E TEST", "${session.myUserId.take(10)} invites ${newSession.myUserId.take(10)}")
|
||||||
|
room.membershipService().invite(newSession.myUserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// All user should accept invite
|
||||||
|
newSessions.forEach { newSession ->
|
||||||
|
waitForAndAcceptInviteInRoom(newSession, roomId)
|
||||||
|
Log.v("#E2E TEST", "${newSession.myUserId.take(10)} joined room $roomId")
|
||||||
|
}
|
||||||
|
ensureMembersHaveJoined(session, newSessions, roomId)
|
||||||
|
return newSessions
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun ensureMembersHaveJoined(session: Session, invitedUserSessions: List<Session>, roomId: String) {
|
||||||
|
testHelper.retryWithBackoff(
|
||||||
|
onFail = {
|
||||||
|
fail("Members ${invitedUserSessions.map { it.myUserId.take(10) }} should have join from the pov of ${session.myUserId.take(10)}")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
invitedUserSessions.map { invitedUserSession ->
|
||||||
|
session.roomService().getRoomMember(invitedUserSession.myUserId, roomId)?.membership?.also {
|
||||||
|
Log.v("#E2E TEST", "${invitedUserSession.myUserId.take(10)} membership is $it")
|
||||||
|
}
|
||||||
|
}.all {
|
||||||
|
it == Membership.JOIN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun waitForAndAcceptInviteInRoom(session: Session, roomId: String) {
|
||||||
|
testHelper.retryWithBackoff(
|
||||||
|
onFail = {
|
||||||
|
fail("${session.myUserId} cannot see the invite from ${session.myUserId.take(10)}")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
val roomSummary = session.getRoomSummary(roomId)
|
||||||
|
(roomSummary != null && roomSummary.membership == Membership.INVITE).also {
|
||||||
|
if (it) {
|
||||||
|
Log.v("#E2E TEST", "${session.myUserId.take(10)} can see the invite from ${roomSummary?.inviterId}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// not sure why it's taking so long :/
|
||||||
|
Log.v("#E2E TEST", "${session.myUserId.take(10)} tries to join room $roomId")
|
||||||
|
try {
|
||||||
|
session.roomService().joinRoom(roomId)
|
||||||
|
} catch (ex: JoinRoomFailure.JoinedWithTimeout) {
|
||||||
|
// it's ok we will wait after
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.v("#E2E TEST", "${session.myUserId} waiting for join echo ...")
|
||||||
|
testHelper.retryWithBackoff(
|
||||||
|
onFail = {
|
||||||
|
fail("${session.myUserId.take(10)} cannot see the join echo for ${roomId}")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
val roomSummary = session.getRoomSummary(roomId)
|
||||||
|
roomSummary != null && roomSummary.membership == Membership.JOIN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Alice and Bob sessions
|
* @return Alice and Bob sessions
|
||||||
*/
|
*/
|
||||||
@ -189,7 +263,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||||||
return MegolmBackupCreationInfo(
|
return MegolmBackupCreationInfo(
|
||||||
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
|
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
|
||||||
authData = createFakeMegolmBackupAuthData(),
|
authData = createFakeMegolmBackupAuthData(),
|
||||||
recoveryKey = "fake"
|
recoveryKey = BackupUtils.recoveryKeyFromPassphrase("3cnTdW")!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,7 +295,6 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun initializeCrossSigning(session: Session) {
|
suspend fun initializeCrossSigning(session: Session) {
|
||||||
testHelper.waitForCallback<Unit> {
|
|
||||||
session.cryptoService().crossSigningService()
|
session.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(
|
.initializeCrossSigning(
|
||||||
object : UserInteractiveAuthInterceptor {
|
object : UserInteractiveAuthInterceptor {
|
||||||
@ -234,9 +307,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, it
|
})
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -272,16 +343,13 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// set up megolm backup
|
// set up megolm backup
|
||||||
val creationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> {
|
val creationInfo = session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null)
|
||||||
session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
|
val version = session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo)
|
||||||
}
|
|
||||||
val version = testHelper.waitForCallback<KeysVersion> {
|
|
||||||
session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
|
|
||||||
}
|
|
||||||
// Save it for gossiping
|
// Save it for gossiping
|
||||||
session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
|
session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
|
||||||
|
|
||||||
extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret ->
|
creationInfo.recoveryKey.toBase64().let { secret ->
|
||||||
ssssService.storeSecret(
|
ssssService.storeSecret(
|
||||||
KEYBACKUP_SECRET_SSSS_NAME,
|
KEYBACKUP_SECRET_SSSS_NAME,
|
||||||
secret,
|
secret,
|
||||||
@ -298,61 +366,78 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||||||
val bobVerificationService = bob.cryptoService().verificationService()
|
val bobVerificationService = bob.cryptoService().verificationService()
|
||||||
|
|
||||||
val localId = UUID.randomUUID().toString()
|
val localId = UUID.randomUUID().toString()
|
||||||
aliceVerificationService.requestKeyVerificationInDMs(
|
val requestID = aliceVerificationService.requestKeyVerificationInDMs(
|
||||||
localId = localId,
|
localId = localId,
|
||||||
methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||||
otherUserId = bob.myUserId,
|
otherUserId = bob.myUserId,
|
||||||
roomId = roomId
|
roomId = roomId
|
||||||
).transactionId
|
).transactionId
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff(
|
||||||
|
onFail = {
|
||||||
|
fail("Bob should see an incoming request from alice")
|
||||||
|
}
|
||||||
|
) {
|
||||||
bobVerificationService.getExistingVerificationRequests(alice.myUserId).firstOrNull {
|
bobVerificationService.getExistingVerificationRequests(alice.myUserId).firstOrNull {
|
||||||
it.requestInfo?.fromDevice == alice.sessionParams.deviceId
|
it.otherDeviceId == alice.sessionParams.deviceId
|
||||||
} != null
|
} != null
|
||||||
}
|
}
|
||||||
|
|
||||||
val incomingRequest = bobVerificationService.getExistingVerificationRequests(alice.myUserId).first {
|
val incomingRequest = bobVerificationService.getExistingVerificationRequests(alice.myUserId).first {
|
||||||
it.requestInfo?.fromDevice == alice.sessionParams.deviceId
|
it.otherDeviceId == alice.sessionParams.deviceId
|
||||||
}
|
}
|
||||||
bobVerificationService.readyPendingVerificationInDMs(listOf(VerificationMethod.SAS), alice.myUserId, roomId, incomingRequest.transactionId!!)
|
|
||||||
|
|
||||||
var requestID: String? = null
|
Timber.v("#TEST Incoming request is $incomingRequest")
|
||||||
|
|
||||||
|
Timber.v("#TEST let bob ready the verification with SAS method")
|
||||||
|
bobVerificationService.readyPendingVerification(
|
||||||
|
listOf(VerificationMethod.SAS),
|
||||||
|
alice.myUserId,
|
||||||
|
incomingRequest.transactionId
|
||||||
|
)
|
||||||
|
|
||||||
// wait for it to be readied
|
// wait for it to be readied
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff(
|
||||||
val outgoingRequest = aliceVerificationService.getExistingVerificationRequests(bob.myUserId)
|
onFail = {
|
||||||
.firstOrNull { it.localId == localId }
|
fail("Alice should see the verification in ready state")
|
||||||
if (outgoingRequest?.isReady == true) {
|
}
|
||||||
requestID = outgoingRequest.transactionId!!
|
) {
|
||||||
true
|
val outgoingRequest = aliceVerificationService.getExistingVerificationRequest(bob.myUserId, requestID)
|
||||||
} else {
|
outgoingRequest?.state == EVerificationState.Ready
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
aliceVerificationService.beginKeyVerificationInDMs(
|
Timber.v("#TEST let alice start the verification")
|
||||||
|
aliceVerificationService.startKeyVerification(
|
||||||
VerificationMethod.SAS,
|
VerificationMethod.SAS,
|
||||||
requestID!!,
|
|
||||||
roomId,
|
|
||||||
bob.myUserId,
|
bob.myUserId,
|
||||||
bob.sessionParams.credentials.deviceId!!
|
requestID,
|
||||||
)
|
)
|
||||||
|
|
||||||
// we should reach SHOW SAS on both
|
// we should reach SHOW SAS on both
|
||||||
var alicePovTx: OutgoingSasVerificationTransaction? = null
|
var alicePovTx: SasVerificationTransaction? = null
|
||||||
var bobPovTx: IncomingSasVerificationTransaction? = null
|
var bobPovTx: SasVerificationTransaction? = null
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff(
|
||||||
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID!!) as? OutgoingSasVerificationTransaction
|
onFail = {
|
||||||
Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}")
|
fail("Alice should should see a verification code")
|
||||||
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
}
|
||||||
|
) {
|
||||||
|
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID)
|
||||||
|
as? SasVerificationTransaction
|
||||||
|
Log.v("TEST", "== alicePovTx id:${requestID} is ${alicePovTx?.state()}")
|
||||||
|
alicePovTx?.getDecimalCodeRepresentation() != null
|
||||||
}
|
}
|
||||||
// wait for alice to get the ready
|
// wait for alice to get the ready
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff(
|
||||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID!!) as? IncomingSasVerificationTransaction
|
onFail = {
|
||||||
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
|
fail("Bob should should see a verification code")
|
||||||
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
}
|
||||||
bobPovTx?.performAccept()
|
) {
|
||||||
}
|
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID)
|
||||||
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
as? SasVerificationTransaction
|
||||||
|
Log.v("TEST", "== bobPovTx is ${bobPovTx?.state()}")
|
||||||
|
// bobPovTx?.state == VerificationTxState.ShortCodeReady
|
||||||
|
bobPovTx?.getDecimalCodeRepresentation() != null
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals("SAS code do not match", alicePovTx!!.getDecimalCodeRepresentation(), bobPovTx!!.getDecimalCodeRepresentation())
|
assertEquals("SAS code do not match", alicePovTx!!.getDecimalCodeRepresentation(), bobPovTx!!.getDecimalCodeRepresentation())
|
||||||
@ -360,11 +445,11 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||||||
bobPovTx!!.userHasVerifiedShortCode()
|
bobPovTx!!.userHasVerifiedShortCode()
|
||||||
alicePovTx!!.userHasVerifiedShortCode()
|
alicePovTx!!.userHasVerifiedShortCode()
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff {
|
||||||
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
|
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff {
|
||||||
bob.cryptoService().crossSigningService().isUserTrusted(alice.myUserId)
|
bob.cryptoService().crossSigningService().isUserTrusted(alice.myUserId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ class DecryptRedactedEventTest : InstrumentedTest {
|
|||||||
roomALicePOV.sendService().redactEvent(timelineEvent.root, redactionReason)
|
roomALicePOV.sendService().redactEvent(timelineEvent.root, redactionReason)
|
||||||
|
|
||||||
// get the event from bob
|
// get the event from bob
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff {
|
||||||
bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)?.root?.isRedacted() == true
|
bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)?.root?.isRedacted() == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,10 +27,6 @@ import org.junit.runners.JUnit4
|
|||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
@ -217,15 +213,11 @@ class E2EShareKeysConfigTest : InstrumentedTest {
|
|||||||
Log.v("#E2E TEST", "Create and start key backup for bob ...")
|
Log.v("#E2E TEST", "Create and start key backup for bob ...")
|
||||||
val keysBackupService = aliceSession.cryptoService().keysBackupService()
|
val keysBackupService = aliceSession.cryptoService().keysBackupService()
|
||||||
val keyBackupPassword = "FooBarBaz"
|
val keyBackupPassword = "FooBarBaz"
|
||||||
val megolmBackupCreationInfo = commonTestHelper.waitForCallback<MegolmBackupCreationInfo> {
|
val megolmBackupCreationInfo = keysBackupService.prepareKeysBackupVersion(keyBackupPassword, null)
|
||||||
keysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it)
|
val version = keysBackupService.createKeysBackupVersion(megolmBackupCreationInfo)
|
||||||
}
|
|
||||||
val version = commonTestHelper.waitForCallback<KeysVersion> {
|
|
||||||
keysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
commonTestHelper.waitForCallback<Unit> {
|
commonTestHelper.retryPeriodically {
|
||||||
keysBackupService.backupAllGroupSessions(null, it)
|
keysBackupService.getTotalNumbersOfKeys() == keysBackupService.getTotalNumbersOfBackedUpKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
// signout
|
// signout
|
||||||
@ -235,20 +227,15 @@ class E2EShareKeysConfigTest : InstrumentedTest {
|
|||||||
newAliceSession.cryptoService().enableShareKeyOnInvite(true)
|
newAliceSession.cryptoService().enableShareKeyOnInvite(true)
|
||||||
|
|
||||||
newAliceSession.cryptoService().keysBackupService().let { kbs ->
|
newAliceSession.cryptoService().keysBackupService().let { kbs ->
|
||||||
val keyVersionResult = commonTestHelper.waitForCallback<KeysVersionResult?> {
|
val keyVersionResult = kbs.getVersion(version.version)
|
||||||
kbs.getVersion(version.version, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
val importedResult = commonTestHelper.waitForCallback<ImportRoomKeysResult> {
|
val importedResult = kbs.restoreKeyBackupWithPassword(
|
||||||
kbs.restoreKeyBackupWithPassword(
|
|
||||||
keyVersionResult!!,
|
keyVersionResult!!,
|
||||||
keyBackupPassword,
|
keyBackupPassword,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
it
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(2, importedResult.totalNumberOfKeys)
|
assertEquals(2, importedResult.totalNumberOfKeys)
|
||||||
}
|
}
|
||||||
|
@ -18,12 +18,7 @@ package org.matrix.android.sdk.internal.crypto
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Deferred
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.awaitAll
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
||||||
import org.amshove.kluent.fail
|
import org.amshove.kluent.fail
|
||||||
import org.amshove.kluent.internal.assertEquals
|
import org.amshove.kluent.internal.assertEquals
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
@ -40,17 +35,6 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
|||||||
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
@ -92,7 +76,6 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val e2eRoomID = cryptoTestData.roomId
|
val e2eRoomID = cryptoTestData.roomId
|
||||||
|
|
||||||
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
|
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
|
||||||
// we want to disable key gossiping to just check initial sending of keys
|
// we want to disable key gossiping to just check initial sending of keys
|
||||||
aliceSession.cryptoService().enableKeyGossiping(false)
|
aliceSession.cryptoService().enableKeyGossiping(false)
|
||||||
@ -100,70 +83,50 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
|
|
||||||
// add some more users and invite them
|
// add some more users and invite them
|
||||||
val otherAccounts = listOf("benoit", "valere", "ganfra") // , "adam", "manu")
|
val otherAccounts = listOf("benoit", "valere", "ganfra") // , "adam", "manu")
|
||||||
.map {
|
.let {
|
||||||
testHelper.createAccount(it, SessionTestParams(true)).also {
|
cryptoTestHelper.inviteNewUsersAndWaitForThemToJoin(aliceSession, e2eRoomID, it)
|
||||||
it.cryptoService().enableKeyGossiping(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.v("#E2E TEST", "All accounts created")
|
|
||||||
// we want to invite them in the room
|
|
||||||
otherAccounts.forEach {
|
|
||||||
Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
|
|
||||||
aliceRoomPOV.membershipService().invite(it.myUserId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// All user should accept invite
|
|
||||||
otherAccounts.forEach { otherSession ->
|
|
||||||
testHelper.waitForAndAcceptInviteInRoom(otherSession, e2eRoomID)
|
|
||||||
Log.v("#E2E TEST", "${otherSession.myUserId} joined room $e2eRoomID")
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that alice see them as joined (not really necessary?)
|
|
||||||
ensureMembersHaveJoined(testHelper, aliceSession, otherAccounts, e2eRoomID)
|
|
||||||
|
|
||||||
Log.v("#E2E TEST", "All users have joined the room")
|
Log.v("#E2E TEST", "All users have joined the room")
|
||||||
Log.v("#E2E TEST", "Alice is sending the message")
|
Log.v("#E2E TEST", "Alice is sending the message")
|
||||||
|
|
||||||
val text = "This is my message"
|
val text = "This is my message"
|
||||||
val sentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, text)
|
val sentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, text)
|
||||||
// val sentEvent = testHelper.sendTextMessage(aliceRoomPOV, "Hello all", 1).first()
|
|
||||||
Assert.assertTrue("Message should be sent", sentEventId != null)
|
Assert.assertTrue("Message should be sent", sentEventId != null)
|
||||||
|
|
||||||
// All should be able to decrypt
|
// All should be able to decrypt
|
||||||
otherAccounts.forEach { otherSession ->
|
otherAccounts.forEach { otherSession ->
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff(
|
||||||
val timeLineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!)
|
onFail = {
|
||||||
|
fail("${otherSession.myUserId.take(10)} should be able to decrypt")
|
||||||
|
}) {
|
||||||
|
val timeLineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also {
|
||||||
|
Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}|${it?.root?.mxDecryptionResult?.isSafe}")
|
||||||
|
}
|
||||||
timeLineEvent != null &&
|
timeLineEvent != null &&
|
||||||
timeLineEvent.isEncrypted() &&
|
timeLineEvent.isEncrypted() &&
|
||||||
timeLineEvent.root.getClearType() == EventType.MESSAGE &&
|
timeLineEvent.root.getClearType() == EventType.MESSAGE &&
|
||||||
timeLineEvent.root.mxDecryptionResult?.isSafe == true
|
timeLineEvent.root.mxDecryptionResult?.isSafe == true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Log.v("#E2E TEST", "Everybody received the encrypted message and could decrypt")
|
||||||
// Add a new user to the room, and check that he can't decrypt
|
// Add a new user to the room, and check that he can't decrypt
|
||||||
|
Log.v("#E2E TEST", "Create some new accounts and invite them")
|
||||||
val newAccount = listOf("adam") // , "adam", "manu")
|
val newAccount = listOf("adam") // , "adam", "manu")
|
||||||
.map {
|
.let {
|
||||||
testHelper.createAccount(it, SessionTestParams(true))
|
cryptoTestHelper.inviteNewUsersAndWaitForThemToJoin(aliceSession, e2eRoomID, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
newAccount.forEach {
|
|
||||||
Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
|
|
||||||
aliceRoomPOV.membershipService().invite(it.myUserId)
|
|
||||||
}
|
|
||||||
|
|
||||||
newAccount.forEach {
|
|
||||||
testHelper.waitForAndAcceptInviteInRoom(it, e2eRoomID)
|
|
||||||
}
|
|
||||||
|
|
||||||
ensureMembersHaveJoined(testHelper, aliceSession, newAccount, e2eRoomID)
|
|
||||||
|
|
||||||
// wait a bit
|
// wait a bit
|
||||||
delay(3_000)
|
delay(3_000)
|
||||||
|
|
||||||
// check that messages are encrypted (uisi)
|
// check that messages are encrypted (uisi)
|
||||||
newAccount.forEach { otherSession ->
|
newAccount.forEach { otherSession ->
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff(
|
||||||
|
onFail = {
|
||||||
|
fail("New Users shouldn't be able to decrypt history")
|
||||||
|
}
|
||||||
|
) {
|
||||||
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also {
|
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also {
|
||||||
Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
|
Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
|
||||||
}
|
}
|
||||||
@ -181,7 +144,12 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
|
|
||||||
// new members should be able to decrypt it
|
// new members should be able to decrypt it
|
||||||
newAccount.forEach { otherSession ->
|
newAccount.forEach { otherSession ->
|
||||||
testHelper.retryPeriodically {
|
// ("${otherSession.myUserId} should be able to decrypt")
|
||||||
|
testHelper.retryWithBackoff(
|
||||||
|
onFail = {
|
||||||
|
fail("New user ${otherSession.myUserId.take(10)} should be able to decrypt the second message")
|
||||||
|
}
|
||||||
|
) {
|
||||||
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(secondSentEventId!!).also {
|
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(secondSentEventId!!).also {
|
||||||
Log.v("#E2E TEST", "Second Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
|
Log.v("#E2E TEST", "Second Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
|
||||||
}
|
}
|
||||||
@ -223,13 +191,10 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
Log.v("#E2E TEST", "Create and start key backup for bob ...")
|
Log.v("#E2E TEST", "Create and start key backup for bob ...")
|
||||||
val bobKeysBackupService = bobSession.cryptoService().keysBackupService()
|
val bobKeysBackupService = bobSession.cryptoService().keysBackupService()
|
||||||
val keyBackupPassword = "FooBarBaz"
|
val keyBackupPassword = "FooBarBaz"
|
||||||
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> {
|
val megolmBackupCreationInfo = bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null)
|
||||||
bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it)
|
val version = bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo)
|
||||||
}
|
|
||||||
val version = testHelper.waitForCallback<KeysVersion> {
|
Log.v("#E2E TEST", "... Key backup started and enabled for bob: version:$version")
|
||||||
bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
|
||||||
}
|
|
||||||
Log.v("#E2E TEST", "... Key backup started and enabled for bob")
|
|
||||||
// Bob session should now have
|
// Bob session should now have
|
||||||
|
|
||||||
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
|
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
|
||||||
@ -242,7 +207,11 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
sentEventIds.add(it)
|
sentEventIds.add(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff(
|
||||||
|
onFail = {
|
||||||
|
fail("Bob should be able to decrypt all messages")
|
||||||
|
}
|
||||||
|
) {
|
||||||
val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
||||||
timeLineEvent != null &&
|
timeLineEvent != null &&
|
||||||
timeLineEvent.isEncrypted() &&
|
timeLineEvent.isEncrypted() &&
|
||||||
@ -256,7 +225,14 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
// Let's wait a bit to be sure that bob has backed up the session
|
// Let's wait a bit to be sure that bob has backed up the session
|
||||||
|
|
||||||
Log.v("#E2E TEST", "Force key backup for Bob...")
|
Log.v("#E2E TEST", "Force key backup for Bob...")
|
||||||
testHelper.waitForCallback<Unit> { bobKeysBackupService.backupAllGroupSessions(null, it) }
|
testHelper.retryWithBackoff(
|
||||||
|
onFail = {
|
||||||
|
fail("All keys should be backedup")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Log.v("#E2E TEST", "backedUp=${ bobKeysBackupService.getTotalNumbersOfBackedUpKeys()}, known=${bobKeysBackupService.getTotalNumbersOfKeys()}")
|
||||||
|
bobKeysBackupService.getTotalNumbersOfBackedUpKeys() == bobKeysBackupService.getTotalNumbersOfKeys()
|
||||||
|
}
|
||||||
Log.v("#E2E TEST", "... Key backup done for Bob")
|
Log.v("#E2E TEST", "... Key backup done for Bob")
|
||||||
|
|
||||||
// Now lets logout both alice and bob to ensure that we won't have any gossiping
|
// Now lets logout both alice and bob to ensure that we won't have any gossiping
|
||||||
@ -276,7 +252,7 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
// check that bob can't currently decrypt
|
// check that bob can't currently decrypt
|
||||||
Log.v("#E2E TEST", "check that bob can't currently decrypt")
|
Log.v("#E2E TEST", "check that bob can't currently decrypt")
|
||||||
sentEventIds.forEach { sentEventId ->
|
sentEventIds.forEach { sentEventId ->
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff {
|
||||||
val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)?.also {
|
val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)?.also {
|
||||||
Log.v("#E2E TEST", "Event seen by new user ${it.root.getClearType()}|${it.root.mCryptoError}")
|
Log.v("#E2E TEST", "Event seen by new user ${it.root.getClearType()}|${it.root.mCryptoError}")
|
||||||
}
|
}
|
||||||
@ -284,33 +260,37 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// after initial sync events are not decrypted, so we have to try manually
|
// after initial sync events are not decrypted, so we have to try manually
|
||||||
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
|
// TODO CHANGE WHEN AVAILABLE FROM RUST
|
||||||
|
cryptoTestHelper.ensureCannotDecrypt(
|
||||||
|
sentEventIds,
|
||||||
|
newBobSession,
|
||||||
|
e2eRoomID,
|
||||||
|
MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID
|
||||||
|
) // MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
|
||||||
|
|
||||||
// Let's now import keys from backup
|
// Let's now import keys from backup
|
||||||
|
Log.v("#E2E TEST", "Restore backup for the new session")
|
||||||
newBobSession.cryptoService().keysBackupService().let { kbs ->
|
newBobSession.cryptoService().keysBackupService().let { kbs ->
|
||||||
val keyVersionResult = testHelper.waitForCallback<KeysVersionResult?> {
|
val keyVersionResult = kbs.getVersion(version.version)
|
||||||
kbs.getVersion(version.version, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
val importedResult = testHelper.waitForCallback<ImportRoomKeysResult> {
|
val importedResult = kbs.restoreKeyBackupWithPassword(
|
||||||
kbs.restoreKeyBackupWithPassword(
|
keyVersionResult!!,
|
||||||
keyVersionResult!!,
|
keyBackupPassword,
|
||||||
keyBackupPassword,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
)
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(3, importedResult.totalNumberOfKeys)
|
assertEquals(3, importedResult.totalNumberOfKeys)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure bob can now decrypt
|
// ensure bob can now decrypt
|
||||||
|
|
||||||
|
Log.v("#E2E TEST", "Check that bob can decrypt now")
|
||||||
cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
|
cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
|
||||||
|
|
||||||
// Check key trust
|
// Check key trust
|
||||||
|
Log.v("#E2E TEST", "Check key safety")
|
||||||
sentEventIds.forEach { sentEventId ->
|
sentEventIds.forEach { sentEventId ->
|
||||||
val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)!!
|
val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)!!
|
||||||
val result = newBobSession.cryptoService().decryptEvent(timelineEvent.root, "")
|
val result = newBobSession.cryptoService().decryptEvent(timelineEvent.root, "")
|
||||||
@ -342,7 +322,11 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
sentEventIds.add(it)
|
sentEventIds.add(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff(
|
||||||
|
onFail = {
|
||||||
|
fail("${bobSession.myUserId.take(10)} should be able to decrypt message sent by alice}")
|
||||||
|
}
|
||||||
|
) {
|
||||||
val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
||||||
timeLineEvent != null &&
|
timeLineEvent != null &&
|
||||||
timeLineEvent.isEncrypted() &&
|
timeLineEvent.isEncrypted() &&
|
||||||
@ -366,7 +350,7 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
// Try to request
|
// Try to request
|
||||||
sentEventIds.forEach { sentEventId ->
|
sentEventIds.forEach { sentEventId ->
|
||||||
val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
|
val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
|
||||||
newBobSession.cryptoService().requestRoomKeyForEvent(event)
|
newBobSession.cryptoService().reRequestRoomKeyForEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that new bob still can't decrypt (keys must have been withheld)
|
// Ensure that new bob still can't decrypt (keys must have been withheld)
|
||||||
@ -391,12 +375,14 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// */
|
||||||
|
|
||||||
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
|
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
|
||||||
|
|
||||||
// Now mark new bob session as verified
|
// Now mark new bob session as verified
|
||||||
|
|
||||||
bobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId!!)
|
bobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId)
|
||||||
newBobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(bobSession.myUserId, bobSession.sessionParams.deviceId!!)
|
newBobSession.cryptoService().verificationService().markedLocallyAsManuallyVerified(bobSession.myUserId, bobSession.sessionParams.deviceId)
|
||||||
|
|
||||||
// now let new session re-request
|
// now let new session re-request
|
||||||
sentEventIds.forEach { sentEventId ->
|
sentEventIds.forEach { sentEventId ->
|
||||||
@ -431,7 +417,7 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
firstMessage.let { text ->
|
firstMessage.let { text ->
|
||||||
firstEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
|
firstEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff {
|
||||||
val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
|
val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
|
||||||
timeLineEvent != null &&
|
timeLineEvent != null &&
|
||||||
timeLineEvent.isEncrypted() &&
|
timeLineEvent.isEncrypted() &&
|
||||||
@ -457,7 +443,7 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
secondMessage.let { text ->
|
secondMessage.let { text ->
|
||||||
secondEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
|
secondEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff {
|
||||||
val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
|
val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
|
||||||
timeLineEvent != null &&
|
timeLineEvent != null &&
|
||||||
timeLineEvent.isEncrypted() &&
|
timeLineEvent.isEncrypted() &&
|
||||||
@ -488,11 +474,11 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
// Now let's verify bobs session, and re-request keys
|
// Now let's verify bobs session, and re-request keys
|
||||||
bobSessionWithBetterKey.cryptoService()
|
bobSessionWithBetterKey.cryptoService()
|
||||||
.verificationService()
|
.verificationService()
|
||||||
.markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId!!)
|
.markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId)
|
||||||
|
|
||||||
newBobSession.cryptoService()
|
newBobSession.cryptoService()
|
||||||
.verificationService()
|
.verificationService()
|
||||||
.markedLocallyAsManuallyVerified(bobSessionWithBetterKey.myUserId, bobSessionWithBetterKey.sessionParams.deviceId!!)
|
.markedLocallyAsManuallyVerified(bobSessionWithBetterKey.myUserId, bobSessionWithBetterKey.sessionParams.deviceId)
|
||||||
|
|
||||||
// now let new session request
|
// now let new session request
|
||||||
newBobSession.cryptoService().reRequestRoomKeyForEvent(firstEventNewBobPov.root)
|
newBobSession.cryptoService().reRequestRoomKeyForEvent(firstEventNewBobPov.root)
|
||||||
@ -501,7 +487,7 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
|
|
||||||
// old session should have shared the key at earliest known index now
|
// old session should have shared the key at earliest known index now
|
||||||
// we should be able to decrypt both
|
// we should be able to decrypt both
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff {
|
||||||
val canDecryptFirst = try {
|
val canDecryptFirst = try {
|
||||||
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
|
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
|
||||||
true
|
true
|
||||||
@ -524,7 +510,7 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
|
|
||||||
val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
|
val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
|
||||||
timeline.start()
|
timeline.start()
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff {
|
||||||
val decryptedMsg = timeline.getSnapshot()
|
val decryptedMsg = timeline.getSnapshot()
|
||||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||||
.also { list ->
|
.also { list ->
|
||||||
@ -543,76 +529,76 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
/**
|
/**
|
||||||
* Test that if a better key is forwared (lower index, it is then used)
|
* Test that if a better key is forwared (lower index, it is then used)
|
||||||
*/
|
*/
|
||||||
@Test
|
// @Test
|
||||||
fun testASelfInteractiveVerificationAndGossip() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
// fun testASelfInteractiveVerificationAndGossip() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||||
|
//
|
||||||
val aliceSession = testHelper.createAccount("alice", SessionTestParams(true))
|
// val aliceSession = testHelper.createAccount("alice", SessionTestParams(true))
|
||||||
cryptoTestHelper.bootstrapSecurity(aliceSession)
|
// cryptoTestHelper.bootstrapSecurity(aliceSession)
|
||||||
|
//
|
||||||
// now let's create a new login from alice
|
// // now let's create a new login from alice
|
||||||
|
//
|
||||||
val aliceNewSession = testHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
|
// val aliceNewSession = testHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
|
||||||
|
//
|
||||||
val deferredOldCode = aliceSession.cryptoService().verificationService().readOldVerificationCodeAsync(this, aliceSession.myUserId)
|
// val deferredOldCode = aliceSession.cryptoService().verificationService().readOldVerificationCodeAsync(this, aliceSession.myUserId)
|
||||||
val deferredNewCode = aliceNewSession.cryptoService().verificationService().readNewVerificationCodeAsync(this, aliceSession.myUserId)
|
// val deferredNewCode = aliceNewSession.cryptoService().verificationService().readNewVerificationCodeAsync(this, aliceSession.myUserId)
|
||||||
// initiate self verification
|
// // initiate self verification
|
||||||
aliceSession.cryptoService().verificationService().requestKeyVerification(
|
// aliceSession.cryptoService().verificationService().requestSelfKeyVerification(
|
||||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
// listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||||
aliceNewSession.myUserId,
|
// // aliceNewSession.myUserId,
|
||||||
listOf(aliceNewSession.sessionParams.deviceId!!)
|
// // listOf(aliceNewSession.sessionParams.deviceId!!)
|
||||||
)
|
// )
|
||||||
|
//
|
||||||
val (oldCode, newCode) = awaitAll(deferredOldCode, deferredNewCode)
|
// val (oldCode, newCode) = awaitAll(deferredOldCode, deferredNewCode)
|
||||||
|
//
|
||||||
assertEquals("Decimal code should have matched", oldCode, newCode)
|
// assertEquals("Decimal code should have matched", oldCode, newCode)
|
||||||
|
//
|
||||||
// Assert that devices are verified
|
// // Assert that devices are verified
|
||||||
val newDeviceFromOldPov: CryptoDeviceInfo? =
|
// val newDeviceFromOldPov: CryptoDeviceInfo? =
|
||||||
aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId)
|
// aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId)
|
||||||
val oldDeviceFromNewPov: CryptoDeviceInfo? =
|
// val oldDeviceFromNewPov: CryptoDeviceInfo? =
|
||||||
aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
|
// aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
|
||||||
|
//
|
||||||
Assert.assertTrue("new device should be verified from old point of view", newDeviceFromOldPov!!.isVerified)
|
// Assert.assertTrue("new device should be verified from old point of view", newDeviceFromOldPov!!.isVerified)
|
||||||
Assert.assertTrue("old device should be verified from new point of view", oldDeviceFromNewPov!!.isVerified)
|
// Assert.assertTrue("old device should be verified from new point of view", oldDeviceFromNewPov!!.isVerified)
|
||||||
|
//
|
||||||
// wait for secret gossiping to happen
|
// // wait for secret gossiping to happen
|
||||||
testHelper.retryPeriodically {
|
// testHelper.retryPeriodically {
|
||||||
aliceNewSession.cryptoService().crossSigningService().allPrivateKeysKnown()
|
// aliceNewSession.cryptoService().crossSigningService().allPrivateKeysKnown()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
testHelper.retryPeriodically {
|
// testHelper.retryPeriodically {
|
||||||
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null
|
// aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
assertEquals(
|
// assertEquals(
|
||||||
"MSK Private parts should be the same",
|
// "MSK Private parts should be the same",
|
||||||
aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master,
|
// aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master,
|
||||||
aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master
|
// aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master
|
||||||
)
|
// )
|
||||||
assertEquals(
|
// assertEquals(
|
||||||
"USK Private parts should be the same",
|
// "USK Private parts should be the same",
|
||||||
aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user,
|
// aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user,
|
||||||
aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user
|
// aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user
|
||||||
)
|
// )
|
||||||
|
//
|
||||||
assertEquals(
|
// assertEquals(
|
||||||
"SSK Private parts should be the same",
|
// "SSK Private parts should be the same",
|
||||||
aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned,
|
// aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned,
|
||||||
aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned
|
// aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned
|
||||||
)
|
// )
|
||||||
|
//
|
||||||
// Let's check that we have the megolm backup key
|
// // Let's check that we have the megolm backup key
|
||||||
assertEquals(
|
// assertEquals(
|
||||||
"Megolm key should be the same",
|
// "Megolm key should be the same",
|
||||||
aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey,
|
// aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey,
|
||||||
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey
|
// aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey
|
||||||
)
|
// )
|
||||||
assertEquals(
|
// assertEquals(
|
||||||
"Megolm version should be the same",
|
// "Megolm version should be the same",
|
||||||
aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version,
|
// aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version,
|
||||||
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version
|
// aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_EncryptionDoesNotHinderVerification() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
fun test_EncryptionDoesNotHinderVerification() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||||
@ -625,26 +611,23 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
user = aliceSession.myUserId,
|
user = aliceSession.myUserId,
|
||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD
|
||||||
)
|
)
|
||||||
|
|
||||||
val bobAuthParams = UserPasswordAuth(
|
val bobAuthParams = UserPasswordAuth(
|
||||||
user = bobSession!!.myUserId,
|
user = bobSession!!.myUserId,
|
||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD
|
||||||
)
|
)
|
||||||
|
|
||||||
testHelper.waitForCallback {
|
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
promise.resume(aliceAuthParams)
|
||||||
promise.resume(aliceAuthParams)
|
}
|
||||||
}
|
})
|
||||||
}, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
testHelper.waitForCallback {
|
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
promise.resume(bobAuthParams)
|
||||||
promise.resume(bobAuthParams)
|
}
|
||||||
}
|
})
|
||||||
}, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// add a second session for bob but not cross signed
|
// add a second session for bob but not cross signed
|
||||||
|
|
||||||
@ -660,11 +643,11 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
|
|
||||||
// wait for it to be synced back the other side
|
// wait for it to be synced back the other side
|
||||||
Timber.v("#TEST: Wait for message to be synced back")
|
Timber.v("#TEST: Wait for message to be synced back")
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff {
|
||||||
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null
|
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff {
|
||||||
secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null
|
secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -681,11 +664,11 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
|
|
||||||
val secondEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "World")!!
|
val secondEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "World")!!
|
||||||
Timber.v("#TEST: Wait for message to be synced back")
|
Timber.v("#TEST: Wait for message to be synced back")
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff {
|
||||||
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null
|
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff {
|
||||||
secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null
|
secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -693,94 +676,94 @@ class E2eeSanityTests : InstrumentedTest {
|
|||||||
cryptoTestHelper.ensureCannotDecrypt(listOf(secondEvent), secondBobSession, cryptoTestData.roomId)
|
cryptoTestHelper.ensureCannotDecrypt(listOf(secondEvent), secondBobSession, cryptoTestData.roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun VerificationService.readOldVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
|
// private suspend fun VerificationService.readOldVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
|
||||||
return scope.async {
|
// return scope.async {
|
||||||
suspendCancellableCoroutine { continuation ->
|
// suspendCancellableCoroutine { continuation ->
|
||||||
var oldCode: String? = null
|
// var oldCode: String? = null
|
||||||
val listener = object : VerificationService.Listener {
|
// val listener = object : VerificationService.Listener {
|
||||||
|
//
|
||||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
// override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||||
val readyInfo = pr.readyInfo
|
// val readyInfo = pr.readyInfo
|
||||||
if (readyInfo != null) {
|
// if (readyInfo != null) {
|
||||||
beginKeyVerification(
|
// beginKeyVerification(
|
||||||
VerificationMethod.SAS,
|
// VerificationMethod.SAS,
|
||||||
userId,
|
// userId,
|
||||||
readyInfo.fromDevice,
|
// readyInfo.fromDevice,
|
||||||
readyInfo.transactionId
|
// readyInfo.transactionId
|
||||||
|
//
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
// override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
Log.d("##TEST", "exitsingPov: $tx")
|
// Log.d("##TEST", "exitsingPov: $tx")
|
||||||
val sasTx = tx as OutgoingSasVerificationTransaction
|
// val sasTx = tx as OutgoingSasVerificationTransaction
|
||||||
when (sasTx.uxState) {
|
// when (sasTx.uxState) {
|
||||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
// OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||||
// for the test we just accept?
|
// // for the test we just accept?
|
||||||
oldCode = sasTx.getDecimalCodeRepresentation()
|
// oldCode = sasTx.getDecimalCodeRepresentation()
|
||||||
sasTx.userHasVerifiedShortCode()
|
// sasTx.userHasVerifiedShortCode()
|
||||||
}
|
// }
|
||||||
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
// OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||||
removeListener(this)
|
// removeListener(this)
|
||||||
// we can release this latch?
|
// // we can release this latch?
|
||||||
continuation.resume(oldCode!!)
|
// continuation.resume(oldCode!!)
|
||||||
}
|
// }
|
||||||
else -> Unit
|
// else -> Unit
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
addListener(listener)
|
// addListener(listener)
|
||||||
continuation.invokeOnCancellation { removeListener(listener) }
|
// continuation.invokeOnCancellation { removeListener(listener) }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
private suspend fun VerificationService.readNewVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
|
// private suspend fun VerificationService.readNewVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
|
||||||
return scope.async {
|
// return scope.async {
|
||||||
suspendCancellableCoroutine { continuation ->
|
// suspendCancellableCoroutine { continuation ->
|
||||||
var newCode: String? = null
|
// var newCode: String? = null
|
||||||
|
//
|
||||||
val listener = object : VerificationService.Listener {
|
// val listener = object : VerificationService.Listener {
|
||||||
|
//
|
||||||
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
// override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||||
// let's ready
|
// // let's ready
|
||||||
readyPendingVerification(
|
// readyPendingVerification(
|
||||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
// listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||||
userId,
|
// userId,
|
||||||
pr.transactionId!!
|
// pr.transactionId!!
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
var matchOnce = true
|
// var matchOnce = true
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
// override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
Log.d("##TEST", "newPov: $tx")
|
// Log.d("##TEST", "newPov: $tx")
|
||||||
|
//
|
||||||
val sasTx = tx as IncomingSasVerificationTransaction
|
// val sasTx = tx as IncomingSasVerificationTransaction
|
||||||
when (sasTx.uxState) {
|
// when (sasTx.uxState) {
|
||||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
// IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||||
// no need to accept as there was a request first it will auto accept
|
// // no need to accept as there was a request first it will auto accept
|
||||||
}
|
// }
|
||||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
// IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||||
if (matchOnce) {
|
// if (matchOnce) {
|
||||||
sasTx.userHasVerifiedShortCode()
|
// sasTx.userHasVerifiedShortCode()
|
||||||
newCode = sasTx.getDecimalCodeRepresentation()
|
// newCode = sasTx.getDecimalCodeRepresentation()
|
||||||
matchOnce = false
|
// matchOnce = false
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
// IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||||
removeListener(this)
|
// removeListener(this)
|
||||||
continuation.resume(newCode!!)
|
// continuation.resume(newCode!!)
|
||||||
}
|
// }
|
||||||
else -> Unit
|
// else -> Unit
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
addListener(listener)
|
// addListener(listener)
|
||||||
continuation.invokeOnCancellation { removeListener(listener) }
|
// continuation.invokeOnCancellation { removeListener(listener) }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
private suspend fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {
|
private suspend fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryPeriodically {
|
||||||
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
|
import org.amshove.kluent.fail
|
||||||
import org.amshove.kluent.internal.assertEquals
|
import org.amshove.kluent.internal.assertEquals
|
||||||
import org.amshove.kluent.internal.assertNotEquals
|
import org.amshove.kluent.internal.assertNotEquals
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
@ -42,7 +43,6 @@ import org.matrix.android.sdk.api.session.room.model.shouldShareHistory
|
|||||||
import org.matrix.android.sdk.common.CommonTestHelper
|
import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||||
import org.matrix.android.sdk.common.SessionTestParams
|
import org.matrix.android.sdk.common.SessionTestParams
|
||||||
import org.matrix.android.sdk.common.wrapWithTimeout
|
|
||||||
|
|
||||||
@RunWith(JUnit4::class)
|
@RunWith(JUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
@ -99,19 +99,25 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||||||
|
|
||||||
val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, aliceMessageText, testHelper)
|
val aliceMessageId: String? = sendMessageInRoom(aliceRoomPOV, aliceMessageText, testHelper)
|
||||||
Assert.assertTrue("Message should be sent", aliceMessageId != null)
|
Assert.assertTrue("Message should be sent", aliceMessageId != null)
|
||||||
Log.v("#E2E TEST", "Alice sent message to roomId: $e2eRoomID")
|
Log.v("#E2E TEST", "Alice has sent message to roomId: $e2eRoomID")
|
||||||
|
|
||||||
// Bob should be able to decrypt the message
|
// Bob should be able to decrypt the message
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff(
|
||||||
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
|
onFail = {
|
||||||
(timelineEvent != null &&
|
fail("Bob should be able to decrypt $aliceMessageId")
|
||||||
timelineEvent.isEncrypted() &&
|
|
||||||
timelineEvent.root.getClearType() == EventType.MESSAGE &&
|
|
||||||
timelineEvent.root.mxDecryptionResult?.isSafe == true).also {
|
|
||||||
if (it) {
|
|
||||||
Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
) {
|
||||||
|
val timelineEvent = bobSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)?.also {
|
||||||
|
Log.v("#E2E TEST", "Bob sees ${it.root.getClearType()}")
|
||||||
|
}
|
||||||
|
(timelineEvent != null &&
|
||||||
|
timelineEvent.isEncrypted() &&
|
||||||
|
timelineEvent.root.getClearType() == EventType.MESSAGE &&
|
||||||
|
timelineEvent.root.mxDecryptionResult?.isSafe == true).also {
|
||||||
|
if (it) {
|
||||||
|
Log.v("#E2E TEST", "Bob can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new user
|
// Create a new user
|
||||||
@ -135,23 +141,31 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||||||
null
|
null
|
||||||
-> {
|
-> {
|
||||||
// Aris should be able to decrypt the message
|
// Aris should be able to decrypt the message
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff(
|
||||||
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
|
onFail = {
|
||||||
(timelineEvent != null &&
|
fail("Aris should be able to decrypt $aliceMessageId")
|
||||||
timelineEvent.isEncrypted() &&
|
}
|
||||||
timelineEvent.root.getClearType() == EventType.MESSAGE &&
|
) {
|
||||||
timelineEvent.root.mxDecryptionResult?.isSafe == false
|
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)?.timelineService()?.getTimelineEvent(aliceMessageId!!)
|
||||||
).also {
|
(timelineEvent != null &&
|
||||||
if (it) {
|
timelineEvent.isEncrypted() &&
|
||||||
Log.v("#E2E TEST", "Aris can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
|
timelineEvent.root.getClearType() == EventType.MESSAGE &&
|
||||||
}
|
timelineEvent.root.mxDecryptionResult?.isSafe == false
|
||||||
|
).also {
|
||||||
|
if (it) {
|
||||||
|
Log.v("#E2E TEST", "Aris can decrypt the message: ${timelineEvent?.root?.getDecryptedTextSummary()}")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RoomHistoryVisibility.INVITED,
|
RoomHistoryVisibility.INVITED,
|
||||||
RoomHistoryVisibility.JOINED -> {
|
RoomHistoryVisibility.JOINED -> {
|
||||||
// Aris should not even be able to get the message
|
// Aris should not even be able to get the message
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff(
|
||||||
|
onFail = {
|
||||||
|
fail("Aris should not even be able to get the message")
|
||||||
|
}
|
||||||
|
) {
|
||||||
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)
|
val timelineEvent = arisSession.roomService().getRoom(e2eRoomID)
|
||||||
?.timelineService()
|
?.timelineService()
|
||||||
?.getTimelineEvent(aliceMessageId!!)
|
?.getTimelineEvent(aliceMessageId!!)
|
||||||
@ -258,11 +272,17 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||||||
|
|
||||||
// Bob should be able to decrypt the message
|
// Bob should be able to decrypt the message
|
||||||
var firstAliceMessageMegolmSessionId: String? = null
|
var firstAliceMessageMegolmSessionId: String? = null
|
||||||
val bobRoomPov = bobSession.roomService().getRoom(e2eRoomID)
|
val bobRoomPov = bobSession.roomService().getRoom(e2eRoomID)!!
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff(
|
||||||
|
onFail = {
|
||||||
|
fail("Bob should be able to decrypt $aliceMessageId")
|
||||||
|
}
|
||||||
|
) {
|
||||||
val timelineEvent = bobRoomPov
|
val timelineEvent = bobRoomPov
|
||||||
?.timelineService()
|
.timelineService()
|
||||||
?.getTimelineEvent(aliceMessageId!!)
|
.getTimelineEvent(aliceMessageId!!)?.also {
|
||||||
|
Log.v("#E2E TEST ROTATION", "Bob sees ${it.root.getClearType()}")
|
||||||
|
}
|
||||||
(timelineEvent != null &&
|
(timelineEvent != null &&
|
||||||
timelineEvent.isEncrypted() &&
|
timelineEvent.isEncrypted() &&
|
||||||
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
||||||
@ -279,11 +299,17 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||||||
Assert.assertNotNull("megolm session id can't be null", firstAliceMessageMegolmSessionId)
|
Assert.assertNotNull("megolm session id can't be null", firstAliceMessageMegolmSessionId)
|
||||||
|
|
||||||
var secondAliceMessageSessionId: String? = null
|
var secondAliceMessageSessionId: String? = null
|
||||||
sendMessageInRoom(aliceRoomPOV, "Other msg", testHelper)?.let { secondMessage ->
|
sendMessageInRoom(aliceRoomPOV, "Other msg", testHelper)!!.let { secondMessage ->
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff(
|
||||||
|
onFail = {
|
||||||
|
fail("Bob should be able to decrypt the second message $secondMessage")
|
||||||
|
}
|
||||||
|
) {
|
||||||
val timelineEvent = bobRoomPov
|
val timelineEvent = bobRoomPov
|
||||||
?.timelineService()
|
.timelineService()
|
||||||
?.getTimelineEvent(secondMessage)
|
.getTimelineEvent(secondMessage)?.also {
|
||||||
|
Log.v("#E2E TEST ROTATION", "Bob sees ${it.root.getClearType()}")
|
||||||
|
}
|
||||||
(timelineEvent != null &&
|
(timelineEvent != null &&
|
||||||
timelineEvent.isEncrypted() &&
|
timelineEvent.isEncrypted() &&
|
||||||
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
||||||
@ -309,29 +335,44 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||||||
historyVisibilityStr = nextRoomHistoryVisibility.historyVisibilityStr
|
historyVisibilityStr = nextRoomHistoryVisibility.historyVisibilityStr
|
||||||
).toContent()
|
).toContent()
|
||||||
)
|
)
|
||||||
|
Log.v("#E2E TEST ROTATION", "State update sent")
|
||||||
|
|
||||||
// ensure that the state did synced down
|
// ensure that the state did synced down
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff(
|
||||||
aliceRoomPOV.stateService().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)?.content
|
onFail = {
|
||||||
|
fail("Alice state should be updated to ${nextRoomHistoryVisibility.historyVisibilityStr}")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
aliceRoomPOV.stateService().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)
|
||||||
|
?.content
|
||||||
|
?.also {
|
||||||
|
Log.v("#E2E TEST ROTATION", "Alice sees state as $it")
|
||||||
|
}
|
||||||
?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility == nextRoomHistoryVisibility.historyVisibility
|
?.toModel<RoomHistoryVisibilityContent>()?.historyVisibility == nextRoomHistoryVisibility.historyVisibility
|
||||||
}
|
}
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
// testHelper.retryPeriodically {
|
||||||
val roomVisibility = aliceSession.getRoom(e2eRoomID)!!
|
// val roomVisibility = aliceSession.getRoom(e2eRoomID)!!
|
||||||
.stateService()
|
// .stateService()
|
||||||
.getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)
|
// .getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, QueryStringValue.IsEmpty)
|
||||||
?.content
|
// ?.content
|
||||||
?.toModel<RoomHistoryVisibilityContent>()
|
// ?.toModel<RoomHistoryVisibilityContent>()
|
||||||
Log.v("#E2E TEST ROTATION", "Room visibility changed from: ${initRoomHistoryVisibility.name} to: ${roomVisibility?.historyVisibility?.name}")
|
// Log.v("#E2E TEST ROTATION", "Room visibility changed from: ${initRoomHistoryVisibility.name} to: ${roomVisibility?.historyVisibility?.name}")
|
||||||
roomVisibility?.historyVisibility == nextRoomHistoryVisibility.historyVisibility
|
// roomVisibility?.historyVisibility == nextRoomHistoryVisibility.historyVisibility
|
||||||
}
|
// }
|
||||||
|
|
||||||
var aliceThirdMessageSessionId: String? = null
|
var aliceThirdMessageSessionId: String? = null
|
||||||
sendMessageInRoom(aliceRoomPOV, "Message after visibility change", testHelper)?.let { thirdMessage ->
|
sendMessageInRoom(aliceRoomPOV, "Message after visibility change", testHelper)!!.let { thirdMessage ->
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff(
|
||||||
|
onFail = {
|
||||||
|
fail("Bob should be able to decrypt $thirdMessage")
|
||||||
|
}
|
||||||
|
) {
|
||||||
val timelineEvent = bobRoomPov
|
val timelineEvent = bobRoomPov
|
||||||
?.timelineService()
|
.timelineService()
|
||||||
?.getTimelineEvent(thirdMessage)
|
.getTimelineEvent(thirdMessage)?.also {
|
||||||
|
Log.v("#E2E TEST ROTATION", "Bob sees ${it.root.getClearType()}")
|
||||||
|
}
|
||||||
(timelineEvent != null &&
|
(timelineEvent != null &&
|
||||||
timelineEvent.isEncrypted() &&
|
timelineEvent.isEncrypted() &&
|
||||||
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
timelineEvent.root.getClearType() == EventType.MESSAGE).also {
|
||||||
@ -364,7 +405,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String, testHelper: CommonTestHelper) {
|
private suspend fun ensureMembersHaveJoined(aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String, testHelper: CommonTestHelper) {
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff {
|
||||||
otherAccounts.map {
|
otherAccounts.map {
|
||||||
aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership
|
aliceSession.roomService().getRoomMember(it.myUserId, e2eRoomID)?.membership
|
||||||
}.all {
|
}.all {
|
||||||
@ -374,7 +415,7 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String, testHelper: CommonTestHelper) {
|
private suspend fun waitForAndAcceptInviteInRoom(otherSession: Session, e2eRoomID: String, testHelper: CommonTestHelper) {
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff {
|
||||||
val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID)
|
val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID)
|
||||||
(roomSummary != null && roomSummary.membership == Membership.INVITE).also {
|
(roomSummary != null && roomSummary.membership == Membership.INVITE).also {
|
||||||
if (it) {
|
if (it) {
|
||||||
@ -383,17 +424,15 @@ class E2eeShareKeysHistoryTest : InstrumentedTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapWithTimeout(60_000) {
|
|
||||||
Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID")
|
Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $e2eRoomID")
|
||||||
try {
|
try {
|
||||||
otherSession.roomService().joinRoom(e2eRoomID)
|
otherSession.roomService().joinRoom(e2eRoomID)
|
||||||
} catch (ex: JoinRoomFailure.JoinedWithTimeout) {
|
} catch (ex: JoinRoomFailure.JoinedWithTimeout) {
|
||||||
// it's ok we will wait after
|
// it's ok we will wait after
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...")
|
Log.v("#E2E TEST", "${otherSession.myUserId} waiting for join echo ...")
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryWithBackoff {
|
||||||
val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID)
|
val roomSummary = otherSession.roomService().getRoomSummary(e2eRoomID)
|
||||||
roomSummary != null && roomSummary.membership == Membership.JOIN
|
roomSummary != null && roomSummary.membership == Membership.JOIN
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import org.amshove.kluent.shouldBeNull
|
|||||||
import org.amshove.kluent.shouldBeTrue
|
import org.amshove.kluent.shouldBeTrue
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.matrix.android.sdk.api.util.fromBase64
|
import org.matrix.android.sdk.api.util.fromBase64
|
||||||
|
import org.matrix.android.sdk.api.util.fromBase64Safe
|
||||||
|
|
||||||
@Suppress("SpellCheckingInspection")
|
@Suppress("SpellCheckingInspection")
|
||||||
class ExtensionsKtTest {
|
class ExtensionsKtTest {
|
||||||
|
@ -35,8 +35,6 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
|||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
||||||
import org.matrix.android.sdk.common.SessionTestParams
|
import org.matrix.android.sdk.common.SessionTestParams
|
||||||
@ -54,7 +52,6 @@ class XSigningTest : InstrumentedTest {
|
|||||||
fun test_InitializeAndStoreKeys() = runSessionTest(context()) { testHelper ->
|
fun test_InitializeAndStoreKeys() = runSessionTest(context()) { testHelper ->
|
||||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||||
|
|
||||||
testHelper.waitForCallback<Unit> {
|
|
||||||
aliceSession.cryptoService().crossSigningService()
|
aliceSession.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
.initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
@ -66,10 +63,10 @@ class XSigningTest : InstrumentedTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, it)
|
})
|
||||||
}
|
|
||||||
|
val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
||||||
|
|
||||||
val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
|
||||||
val masterPubKey = myCrossSigningKeys?.masterKey()
|
val masterPubKey = myCrossSigningKeys?.masterKey()
|
||||||
assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey)
|
assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey)
|
||||||
val selfSigningKey = myCrossSigningKeys?.selfSigningKey()
|
val selfSigningKey = myCrossSigningKeys?.selfSigningKey()
|
||||||
@ -79,13 +76,14 @@ class XSigningTest : InstrumentedTest {
|
|||||||
|
|
||||||
assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted() == true)
|
assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted() == true)
|
||||||
|
|
||||||
assertTrue("Signing Keys should be trusted", aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId).isVerified())
|
val userTrustResult = aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId)
|
||||||
|
assertTrue("Signing Keys should be trusted", userTrustResult.isVerified())
|
||||||
|
|
||||||
testHelper.signOutAndClose(aliceSession)
|
testHelper.signOutAndClose(aliceSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test_CrossSigningCheckBobSeesTheKeys() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
fun test_CrossSigningCheckBobSeesTheKeys() = runCryptoTest(context()) { cryptoTestHelper, _ ->
|
||||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
@ -100,39 +98,30 @@ class XSigningTest : InstrumentedTest {
|
|||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD
|
||||||
)
|
)
|
||||||
|
|
||||||
testHelper.waitForCallback<Unit> {
|
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
promise.resume(aliceAuthParams)
|
||||||
promise.resume(aliceAuthParams)
|
}
|
||||||
}
|
})
|
||||||
}, it)
|
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
}
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
testHelper.waitForCallback<Unit> {
|
promise.resume(bobAuthParams)
|
||||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
}
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
})
|
||||||
promise.resume(bobAuthParams)
|
|
||||||
}
|
|
||||||
}, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that alice can see bob keys
|
// Check that alice can see bob keys
|
||||||
testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) }
|
aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobSession.myUserId), true)
|
||||||
|
|
||||||
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
|
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
|
||||||
|
|
||||||
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
|
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
|
||||||
assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey())
|
assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey())
|
||||||
assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey())
|
assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey())
|
||||||
|
|
||||||
assertEquals(
|
val myKeys = bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
||||||
"Bob keys from alice pov should match",
|
|
||||||
bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey,
|
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, myKeys?.masterKey()?.unpaddedBase64PublicKey)
|
||||||
bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey
|
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, myKeys?.selfSigningKey()?.unpaddedBase64PublicKey)
|
||||||
)
|
|
||||||
assertEquals(
|
|
||||||
"Bob keys from alice pov should match",
|
|
||||||
bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey,
|
|
||||||
bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey
|
|
||||||
)
|
|
||||||
|
|
||||||
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
|
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
|
||||||
}
|
}
|
||||||
@ -153,40 +142,34 @@ class XSigningTest : InstrumentedTest {
|
|||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD
|
||||||
)
|
)
|
||||||
|
|
||||||
testHelper.waitForCallback<Unit> {
|
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
promise.resume(aliceAuthParams)
|
||||||
promise.resume(aliceAuthParams)
|
}
|
||||||
}
|
})
|
||||||
}, it)
|
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
}
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
testHelper.waitForCallback<Unit> {
|
promise.resume(bobAuthParams)
|
||||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
}
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
})
|
||||||
promise.resume(bobAuthParams)
|
|
||||||
}
|
|
||||||
}, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that alice can see bob keys
|
// Check that alice can see bob keys
|
||||||
val bobUserId = bobSession.myUserId
|
val bobUserId = bobSession.myUserId
|
||||||
testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) }
|
aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), true)
|
||||||
|
|
||||||
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
|
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
|
||||||
|
|
||||||
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
|
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
|
||||||
|
|
||||||
testHelper.waitForCallback<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) }
|
aliceSession.cryptoService().crossSigningService().trustUser(bobUserId)
|
||||||
|
|
||||||
// Now bobs logs in on a new device and verifies it
|
// Now bobs logs in on a new device and verifies it
|
||||||
// We will want to test that in alice POV, this new device would be trusted by cross signing
|
// We will want to test that in alice POV, this new device would be trusted by cross signing
|
||||||
|
|
||||||
val bobSession2 = testHelper.logIntoAccount(bobUserId, SessionTestParams(true))
|
val bobSession2 = testHelper.logIntoAccount(bobUserId, SessionTestParams(true))
|
||||||
val bobSecondDeviceId = bobSession2.sessionParams.deviceId!!
|
val bobSecondDeviceId = bobSession2.sessionParams.deviceId
|
||||||
|
|
||||||
// Check that bob first session sees the new login
|
// Check that bob first session sees the new login
|
||||||
val data = testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
val data = bobSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), true)
|
||||||
bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
|
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
|
||||||
fail("Bob should see the new device")
|
fail("Bob should see the new device")
|
||||||
@ -196,14 +179,10 @@ class XSigningTest : InstrumentedTest {
|
|||||||
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
|
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
|
||||||
|
|
||||||
// Manually mark it as trusted from first session
|
// Manually mark it as trusted from first session
|
||||||
testHelper.waitForCallback<Unit> {
|
bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId)
|
||||||
bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now alice should cross trust bob's second device
|
// Now alice should cross trust bob's second device
|
||||||
val data2 = testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
val data2 = aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), true)
|
||||||
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that the device is seen
|
// check that the device is seen
|
||||||
if (data2.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
|
if (data2.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
|
||||||
@ -230,20 +209,16 @@ class XSigningTest : InstrumentedTest {
|
|||||||
password = TestConstants.PASSWORD
|
password = TestConstants.PASSWORD
|
||||||
)
|
)
|
||||||
|
|
||||||
testHelper.waitForCallback<Unit> {
|
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
promise.resume(aliceAuthParams)
|
||||||
promise.resume(aliceAuthParams)
|
}
|
||||||
}
|
})
|
||||||
}, it)
|
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
}
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
testHelper.waitForCallback<Unit> {
|
promise.resume(bobAuthParams)
|
||||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
}
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
})
|
||||||
promise.resume(bobAuthParams)
|
|
||||||
}
|
|
||||||
}, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
cryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, cryptoTestData.roomId)
|
cryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, cryptoTestData.roomId)
|
||||||
|
|
||||||
@ -267,13 +242,11 @@ class XSigningTest : InstrumentedTest {
|
|||||||
.getUserCrossSigningKeys(bobSession.myUserId)!!
|
.getUserCrossSigningKeys(bobSession.myUserId)!!
|
||||||
.masterKey()!!.unpaddedBase64PublicKey!!
|
.masterKey()!!.unpaddedBase64PublicKey!!
|
||||||
|
|
||||||
testHelper.waitForCallback<Unit> {
|
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
promise.resume(bobAuthParams)
|
||||||
promise.resume(bobAuthParams)
|
}
|
||||||
}
|
})
|
||||||
}, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryPeriodically {
|
||||||
val newBobMsk = aliceSession.cryptoService().crossSigningService()
|
val newBobMsk = aliceSession.cryptoService().crossSigningService()
|
||||||
|
@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.crypto.gossiping
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
|
import junit.framework.TestCase.assertEquals
|
||||||
import junit.framework.TestCase.assertNotNull
|
import junit.framework.TestCase.assertNotNull
|
||||||
import junit.framework.TestCase.assertTrue
|
import junit.framework.TestCase.assertTrue
|
||||||
import org.amshove.kluent.internal.assertEquals
|
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
@ -100,7 +100,7 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
|
|
||||||
// Try to request
|
// Try to request
|
||||||
aliceSession2.cryptoService().enableKeyGossiping(true)
|
aliceSession2.cryptoService().enableKeyGossiping(true)
|
||||||
aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root)
|
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
||||||
|
|
||||||
val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId
|
val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId
|
||||||
|
|
||||||
@ -163,22 +163,26 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
|
|
||||||
// Mark the device as trusted
|
// Mark the device as trusted
|
||||||
|
|
||||||
Log.v("#TEST", "=======> Alice device 1 is ${aliceSession.sessionParams.deviceId}|${aliceSession.cryptoService().getMyDevice().identityKey()}")
|
Log.v("#TEST", "=======> Alice device 1 is ${aliceSession.sessionParams.deviceId}|${aliceSession.cryptoService().getMyCryptoDevice().identityKey()}")
|
||||||
val aliceSecondSession = aliceSession2.cryptoService().getMyDevice()
|
val aliceSecondSession = aliceSession2.cryptoService().getMyCryptoDevice()
|
||||||
Log.v("#TEST", "=======> Alice device 2 is ${aliceSession2.sessionParams.deviceId}|${aliceSecondSession.identityKey()}")
|
Log.v("#TEST", "=======> Alice device 2 is ${aliceSession2.sessionParams.deviceId}|${aliceSecondSession.identityKey()}")
|
||||||
|
|
||||||
aliceSession.cryptoService().setDeviceVerification(
|
aliceSession.cryptoService().setDeviceVerification(
|
||||||
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
|
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
|
||||||
aliceSession2.sessionParams.deviceId ?: ""
|
aliceSession2.sessionParams.deviceId
|
||||||
)
|
)
|
||||||
|
|
||||||
// We only accept forwards from trusted session, so we need to trust on other side to
|
// We only accept forwards from trusted session, so we need to trust on other side to
|
||||||
aliceSession2.cryptoService().setDeviceVerification(
|
aliceSession2.cryptoService().setDeviceVerification(
|
||||||
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
|
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true), aliceSession.myUserId,
|
||||||
aliceSession.sessionParams.deviceId ?: ""
|
aliceSession.sessionParams.deviceId
|
||||||
)
|
)
|
||||||
|
|
||||||
aliceSession.cryptoService().deviceWithIdentityKey(aliceSecondSession.identityKey()!!, MXCRYPTO_ALGORITHM_OLM)!!.isVerified shouldBeEqualTo true
|
aliceSession.cryptoService().deviceWithIdentityKey(
|
||||||
|
aliceSecondSession.userId,
|
||||||
|
aliceSecondSession.identityKey()!!,
|
||||||
|
MXCRYPTO_ALGORITHM_OLM
|
||||||
|
)!!.isVerified shouldBeEqualTo true
|
||||||
|
|
||||||
// Re request
|
// Re request
|
||||||
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
||||||
@ -257,11 +261,11 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||||
ownDeviceReply != null && ownDeviceReply.result is RequestResult.Success
|
ownDeviceReply != null && ownDeviceReply.result is RequestResult.Success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commonTestHelper.signOutAndClose(aliceSession)
|
||||||
|
commonTestHelper.signOutAndClose(aliceNewSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests that keys reshared with own verified session are done from the earliest known index
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() = runCryptoTest(
|
fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() = runCryptoTest(
|
||||||
context(),
|
context(),
|
||||||
@ -331,10 +335,10 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
// Mark the new session as verified
|
// Mark the new session as verified
|
||||||
aliceSession.cryptoService()
|
aliceSession.cryptoService()
|
||||||
.verificationService()
|
.verificationService()
|
||||||
.markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId!!)
|
.markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId)
|
||||||
aliceNewSession.cryptoService()
|
aliceNewSession.cryptoService()
|
||||||
.verificationService()
|
.verificationService()
|
||||||
.markedLocallyAsManuallyVerified(aliceSession.myUserId, aliceSession.sessionParams.deviceId!!)
|
.markedLocallyAsManuallyVerified(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
|
||||||
|
|
||||||
// Let's now try to request
|
// Let's now try to request
|
||||||
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvents.first().root)
|
aliceNewSession.cryptoService().reRequestRoomKeyForEvent(sentEvents.first().root)
|
||||||
@ -375,9 +379,6 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
commonTestHelper.signOutAndClose(bobSession)
|
commonTestHelper.signOutAndClose(bobSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests that we don't cancel a request to early on first forward if the index is not good enough
|
|
||||||
*/
|
|
||||||
@Test
|
@Test
|
||||||
fun test_dontCancelToEarly() = runCryptoTest(
|
fun test_dontCancelToEarly() = runCryptoTest(
|
||||||
context(),
|
context(),
|
||||||
@ -419,10 +420,10 @@ class KeyShareTests : InstrumentedTest {
|
|||||||
// Mark the new session as verified
|
// Mark the new session as verified
|
||||||
aliceSession.cryptoService()
|
aliceSession.cryptoService()
|
||||||
.verificationService()
|
.verificationService()
|
||||||
.markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId!!)
|
.markedLocallyAsManuallyVerified(aliceNewSession.myUserId, aliceNewSession.sessionParams.deviceId)
|
||||||
aliceNewSession.cryptoService()
|
aliceNewSession.cryptoService()
|
||||||
.verificationService()
|
.verificationService()
|
||||||
.markedLocallyAsManuallyVerified(aliceSession.myUserId, aliceSession.sessionParams.deviceId!!)
|
.markedLocallyAsManuallyVerified(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
|
||||||
|
|
||||||
// /!\ Stop initial alice session syncing so that it can't reply
|
// /!\ Stop initial alice session syncing so that it can't reply
|
||||||
aliceSession.cryptoService().enableKeyGossiping(false)
|
aliceSession.cryptoService().enableKeyGossiping(false)
|
||||||
|
@ -26,7 +26,6 @@ import org.junit.Test
|
|||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
@ -71,6 +70,7 @@ class WithHeldTests : InstrumentedTest {
|
|||||||
val roomAlicePOV = aliceSession.getRoom(roomId)!!
|
val roomAlicePOV = aliceSession.getRoom(roomId)!!
|
||||||
|
|
||||||
val bobUnverifiedSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
|
val bobUnverifiedSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
|
||||||
|
|
||||||
// =============================
|
// =============================
|
||||||
// ACT
|
// ACT
|
||||||
// =============================
|
// =============================
|
||||||
@ -155,15 +155,13 @@ class WithHeldTests : InstrumentedTest {
|
|||||||
val aliceInterceptor = testHelper.getTestInterceptor(aliceSession)
|
val aliceInterceptor = testHelper.getTestInterceptor(aliceSession)
|
||||||
|
|
||||||
// Simulate no OTK
|
// Simulate no OTK
|
||||||
aliceInterceptor!!.addRule(
|
aliceInterceptor!!.addRule(MockOkHttpInterceptor.SimpleRule(
|
||||||
MockOkHttpInterceptor.SimpleRule(
|
"/keys/claim",
|
||||||
"/keys/claim",
|
200,
|
||||||
200,
|
"""
|
||||||
"""
|
|
||||||
{ "one_time_keys" : {} }
|
{ "one_time_keys" : {} }
|
||||||
"""
|
"""
|
||||||
)
|
))
|
||||||
)
|
|
||||||
Log.d("#TEST", "Recovery :${aliceSession.sessionParams.credentials.accessToken}")
|
Log.d("#TEST", "Recovery :${aliceSession.sessionParams.credentials.accessToken}")
|
||||||
|
|
||||||
val roomAlicePov = aliceSession.getRoom(testData.roomId)!!
|
val roomAlicePov = aliceSession.getRoom(testData.roomId)!!
|
||||||
@ -191,10 +189,7 @@ class WithHeldTests : InstrumentedTest {
|
|||||||
|
|
||||||
// Ensure that alice has marked the session to be shared with bob
|
// Ensure that alice has marked the session to be shared with bob
|
||||||
val sessionId = eventBobPOV!!.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
val sessionId = eventBobPOV!!.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||||
val chainIndex = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(
|
val chainIndex = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSession.myUserId, bobSession.sessionParams.credentials.deviceId)
|
||||||
bobSession.myUserId,
|
|
||||||
bobSession.sessionParams.credentials.deviceId
|
|
||||||
)
|
|
||||||
|
|
||||||
Assert.assertEquals("Alice should have marked bob's device for this session", 0, chainIndex)
|
Assert.assertEquals("Alice should have marked bob's device for this session", 0, chainIndex)
|
||||||
// Add a new device for bob
|
// Add a new device for bob
|
||||||
@ -210,10 +205,7 @@ class WithHeldTests : InstrumentedTest {
|
|||||||
bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(secondMessageId) != null
|
bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(secondMessageId) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
val chainIndex2 = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(
|
val chainIndex2 = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSecondSession.myUserId, bobSecondSession.sessionParams.credentials.deviceId)
|
||||||
bobSecondSession.myUserId,
|
|
||||||
bobSecondSession.sessionParams.credentials.deviceId
|
|
||||||
)
|
|
||||||
|
|
||||||
Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2)
|
Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2)
|
||||||
|
|
||||||
@ -243,8 +235,8 @@ class WithHeldTests : InstrumentedTest {
|
|||||||
cryptoTestHelper.initializeCrossSigning(bobSecondSession)
|
cryptoTestHelper.initializeCrossSigning(bobSecondSession)
|
||||||
|
|
||||||
// Trust bob second device from Alice POV
|
// Trust bob second device from Alice POV
|
||||||
aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!, NoOpMatrixCallback())
|
aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId)
|
||||||
bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId!!, NoOpMatrixCallback())
|
bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId)
|
||||||
|
|
||||||
var sessionId: String? = null
|
var sessionId: String? = null
|
||||||
// Check that the
|
// Check that the
|
||||||
|
@ -19,14 +19,13 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
|
|||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper
|
import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
import org.matrix.android.sdk.common.CryptoTestData
|
import org.matrix.android.sdk.common.CryptoTestData
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]
|
* Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]
|
||||||
*/
|
*/
|
||||||
internal data class KeysBackupScenarioData(
|
internal data class KeysBackupScenarioData(
|
||||||
val cryptoTestData: CryptoTestData,
|
val cryptoTestData: CryptoTestData,
|
||||||
val aliceKeys: List<MXInboundMegolmSessionWrapper>,
|
val aliceKeysCount: Int,
|
||||||
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
|
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
|
||||||
val aliceSession2: Session
|
val aliceSession2: Session
|
||||||
) {
|
) {
|
||||||
|
@ -18,13 +18,10 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
|
|||||||
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
|
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper
|
import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||||
import org.matrix.android.sdk.common.assertDictEquals
|
import org.matrix.android.sdk.common.assertDictEquals
|
||||||
@ -53,29 +50,22 @@ internal class KeysBackupTestHelper(
|
|||||||
|
|
||||||
waitForKeybackUpBatching()
|
waitForKeybackUpBatching()
|
||||||
|
|
||||||
val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
// val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||||
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
|
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
|
||||||
|
|
||||||
val stateObserver = StateObserver(keysBackup)
|
val stateObserver = StateObserver(keysBackup)
|
||||||
|
|
||||||
val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100)
|
// val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100)
|
||||||
|
|
||||||
// - Do an e2e backup to the homeserver
|
// - Do an e2e backup to the homeserver
|
||||||
val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password)
|
val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password)
|
||||||
|
|
||||||
var lastProgress = 0
|
testHelper.retryPeriodically {
|
||||||
var lastTotal = 0
|
keysBackup.getTotalNumbersOfKeys() == keysBackup.getTotalNumbersOfBackedUpKeys()
|
||||||
testHelper.waitForCallback<Unit> {
|
|
||||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
|
||||||
override fun onProgress(progress: Int, total: Int) {
|
|
||||||
lastProgress = progress
|
|
||||||
lastTotal = total
|
|
||||||
}
|
|
||||||
}, it)
|
|
||||||
}
|
}
|
||||||
|
val totalNumbersOfBackedUpKeys = cryptoTestData.firstSession.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()
|
||||||
|
|
||||||
Assert.assertEquals(2, lastProgress)
|
Assert.assertEquals(2, totalNumbersOfBackedUpKeys)
|
||||||
Assert.assertEquals(2, lastTotal)
|
|
||||||
|
|
||||||
val aliceUserId = cryptoTestData.firstSession.myUserId
|
val aliceUserId = cryptoTestData.firstSession.myUserId
|
||||||
|
|
||||||
@ -83,19 +73,20 @@ internal class KeysBackupTestHelper(
|
|||||||
val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
|
val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
|
||||||
|
|
||||||
// Test check: aliceSession2 has no keys at login
|
// Test check: aliceSession2 has no keys at login
|
||||||
Assert.assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false))
|
val inboundGroupSessionCount = aliceSession2.cryptoService().inboundGroupSessionsCount(false)
|
||||||
|
Assert.assertEquals(0, inboundGroupSessionCount)
|
||||||
|
|
||||||
// Wait for backup state to be NotTrusted
|
// Wait for backup state to be NotTrusted
|
||||||
waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted)
|
waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted)
|
||||||
|
|
||||||
stateObserver.stopAndCheckStates(null)
|
stateObserver.stopAndCheckStates(null)
|
||||||
|
|
||||||
return KeysBackupScenarioData(
|
val totalNumbersOfBackedUpKeysFromNewSession = aliceSession2.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()
|
||||||
cryptoTestData,
|
|
||||||
aliceKeys,
|
return KeysBackupScenarioData(cryptoTestData,
|
||||||
|
totalNumbersOfBackedUpKeysFromNewSession,
|
||||||
prepareKeysBackupDataResult,
|
prepareKeysBackupDataResult,
|
||||||
aliceSession2
|
aliceSession2)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun prepareAndCreateKeysBackupData(
|
suspend fun prepareAndCreateKeysBackupData(
|
||||||
@ -104,18 +95,15 @@ internal class KeysBackupTestHelper(
|
|||||||
): PrepareKeysBackupDataResult {
|
): PrepareKeysBackupDataResult {
|
||||||
val stateObserver = StateObserver(keysBackup)
|
val stateObserver = StateObserver(keysBackup)
|
||||||
|
|
||||||
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> {
|
val megolmBackupCreationInfo = keysBackup.prepareKeysBackupVersion(password, null)
|
||||||
keysBackup.prepareKeysBackupVersion(password, null, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertNotNull(megolmBackupCreationInfo)
|
Assert.assertNotNull(megolmBackupCreationInfo)
|
||||||
|
|
||||||
Assert.assertFalse("Key backup should not be enabled before creation", keysBackup.isEnabled())
|
Assert.assertFalse("Key backup should not be enabled before creation", keysBackup.isEnabled())
|
||||||
|
|
||||||
// Create the version
|
// Create the version
|
||||||
val keysVersion = testHelper.waitForCallback<KeysVersion> {
|
val keysVersion =
|
||||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo)
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertNotNull("Key backup version should not be null", keysVersion.version)
|
Assert.assertNotNull("Key backup version should not be null", keysVersion.version)
|
||||||
|
|
||||||
@ -152,7 +140,7 @@ internal class KeysBackupTestHelper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) {
|
internal fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) {
|
||||||
Assert.assertNotNull(keys1)
|
Assert.assertNotNull(keys1)
|
||||||
Assert.assertNotNull(keys2)
|
Assert.assertNotNull(keys2)
|
||||||
|
|
||||||
@ -174,24 +162,27 @@ internal class KeysBackupTestHelper(
|
|||||||
* - The new device must have the same count of megolm keys
|
* - The new device must have the same count of megolm keys
|
||||||
* - Alice must have the same keys on both devices
|
* - Alice must have the same keys on both devices
|
||||||
*/
|
*/
|
||||||
fun checkRestoreSuccess(
|
suspend fun checkRestoreSuccess(
|
||||||
testData: KeysBackupScenarioData,
|
testData: KeysBackupScenarioData,
|
||||||
total: Int,
|
total: Int,
|
||||||
imported: Int
|
imported: Int
|
||||||
) {
|
) {
|
||||||
// - Imported keys number must be correct
|
// - Imported keys number must be correct
|
||||||
Assert.assertEquals(testData.aliceKeys.size, total)
|
Assert.assertEquals(testData.aliceKeysCount, total)
|
||||||
Assert.assertEquals(total, imported)
|
Assert.assertEquals(total, imported)
|
||||||
|
|
||||||
// - The new device must have the same count of megolm keys
|
// - The new device must have the same count of megolm keys
|
||||||
Assert.assertEquals(testData.aliceKeys.size, testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false))
|
val inboundGroupSessionCount = testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false)
|
||||||
|
|
||||||
|
Assert.assertEquals(testData.aliceKeysCount, inboundGroupSessionCount)
|
||||||
|
|
||||||
// - Alice must have the same keys on both devices
|
// - Alice must have the same keys on both devices
|
||||||
for (aliceKey1 in testData.aliceKeys) {
|
// TODO can't access internals as we can switch from rust/kotlin
|
||||||
val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
// for (aliceKey1 in testData.aliceKeys) {
|
||||||
.getInboundGroupSession(aliceKey1.safeSessionId!!, aliceKey1.senderKey!!)
|
// val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||||
Assert.assertNotNull(aliceKey2)
|
// .getInboundGroupSession(aliceKey1.safeSessionId!!, aliceKey1.senderKey!!)
|
||||||
assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys())
|
// Assert.assertNotNull(aliceKey2)
|
||||||
}
|
// assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys())
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,611 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Assert.assertFalse
|
|
||||||
import org.junit.Assert.assertNotNull
|
|
||||||
import org.junit.Assert.assertNull
|
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
import org.junit.Assert.fail
|
|
||||||
import org.junit.FixMethodOrder
|
|
||||||
import org.junit.Ignore
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.junit.runners.MethodSorters
|
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
|
||||||
import org.matrix.android.sdk.api.session.Session
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.SasMode
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.toValue
|
|
||||||
import java.util.concurrent.CountDownLatch
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
|
||||||
@Ignore
|
|
||||||
class SASTest : InstrumentedTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_aliceStartThenAliceCancel() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
|
||||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val bobSession = cryptoTestData.secondSession
|
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
|
||||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
|
||||||
|
|
||||||
val bobTxCreatedLatch = CountDownLatch(1)
|
|
||||||
val bobListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
bobTxCreatedLatch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bobVerificationService.addListener(bobListener)
|
|
||||||
|
|
||||||
val txID = aliceVerificationService.beginKeyVerification(
|
|
||||||
VerificationMethod.SAS,
|
|
||||||
bobSession.myUserId,
|
|
||||||
bobSession.cryptoService().getMyDevice().deviceId,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
assertNotNull("Alice should have a started transaction", txID)
|
|
||||||
|
|
||||||
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
|
|
||||||
assertNotNull("Alice should have a started transaction", aliceKeyTx)
|
|
||||||
|
|
||||||
testHelper.await(bobTxCreatedLatch)
|
|
||||||
bobVerificationService.removeListener(bobListener)
|
|
||||||
|
|
||||||
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
|
|
||||||
|
|
||||||
assertNotNull("Bob should have started verif transaction", bobKeyTx)
|
|
||||||
assertTrue(bobKeyTx is SASDefaultVerificationTransaction)
|
|
||||||
assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
|
|
||||||
assertTrue(aliceKeyTx is SASDefaultVerificationTransaction)
|
|
||||||
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
|
|
||||||
|
|
||||||
val aliceSasTx = aliceKeyTx as SASDefaultVerificationTransaction?
|
|
||||||
val bobSasTx = bobKeyTx as SASDefaultVerificationTransaction?
|
|
||||||
|
|
||||||
assertEquals("Alice state should be started", VerificationTxState.Started, aliceSasTx!!.state)
|
|
||||||
assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobSasTx!!.state)
|
|
||||||
|
|
||||||
// Let's cancel from alice side
|
|
||||||
val cancelLatch = CountDownLatch(1)
|
|
||||||
|
|
||||||
val bobListener2 = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
if (tx.transactionId == txID) {
|
|
||||||
val immutableState = (tx as SASDefaultVerificationTransaction).state
|
|
||||||
if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
|
|
||||||
cancelLatch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bobVerificationService.addListener(bobListener2)
|
|
||||||
|
|
||||||
aliceSasTx.cancel(CancelCode.User)
|
|
||||||
testHelper.await(cancelLatch)
|
|
||||||
|
|
||||||
assertTrue("Should be cancelled on alice side", aliceSasTx.state is VerificationTxState.Cancelled)
|
|
||||||
assertTrue("Should be cancelled on bob side", bobSasTx.state is VerificationTxState.Cancelled)
|
|
||||||
|
|
||||||
val aliceCancelState = aliceSasTx.state as VerificationTxState.Cancelled
|
|
||||||
val bobCancelState = bobSasTx.state as VerificationTxState.Cancelled
|
|
||||||
|
|
||||||
assertTrue("Should be cancelled by me on alice side", aliceCancelState.byMe)
|
|
||||||
assertFalse("Should be cancelled by other on bob side", bobCancelState.byMe)
|
|
||||||
|
|
||||||
assertEquals("Should be User cancelled on alice side", CancelCode.User, aliceCancelState.cancelCode)
|
|
||||||
assertEquals("Should be User cancelled on bob side", CancelCode.User, bobCancelState.cancelCode)
|
|
||||||
|
|
||||||
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
|
|
||||||
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore("This test will be ignored until it is fixed")
|
|
||||||
fun test_key_agreement_protocols_must_include_curve25519() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
|
||||||
fail("Not passing for the moment")
|
|
||||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val bobSession = cryptoTestData.secondSession!!
|
|
||||||
|
|
||||||
val protocols = listOf("meh_dont_know")
|
|
||||||
val tid = "00000000"
|
|
||||||
|
|
||||||
// Bob should receive a cancel
|
|
||||||
var cancelReason: CancelCode? = null
|
|
||||||
val cancelLatch = CountDownLatch(1)
|
|
||||||
|
|
||||||
val bobListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
if (tx.transactionId == tid && tx.state is VerificationTxState.Cancelled) {
|
|
||||||
cancelReason = (tx.state as VerificationTxState.Cancelled).cancelCode
|
|
||||||
cancelLatch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bobSession.cryptoService().verificationService().addListener(bobListener)
|
|
||||||
|
|
||||||
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
|
||||||
// TODO override fun onToDeviceEvent(event: Event?) {
|
|
||||||
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
|
||||||
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
|
|
||||||
// TODO canceledToDeviceEvent = event
|
|
||||||
// TODO cancelLatch.countDown()
|
|
||||||
// TODO }
|
|
||||||
// TODO }
|
|
||||||
// TODO }
|
|
||||||
// TODO })
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val aliceUserID = aliceSession.myUserId
|
|
||||||
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
|
|
||||||
|
|
||||||
val aliceListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
|
||||||
(tx as IncomingSasVerificationTransaction).performAccept()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aliceSession.cryptoService().verificationService().addListener(aliceListener)
|
|
||||||
|
|
||||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
|
|
||||||
|
|
||||||
testHelper.await(cancelLatch)
|
|
||||||
|
|
||||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore("This test will be ignored until it is fixed")
|
|
||||||
fun test_key_agreement_macs_Must_include_hmac_sha256() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
|
||||||
fail("Not passing for the moment")
|
|
||||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val bobSession = cryptoTestData.secondSession!!
|
|
||||||
|
|
||||||
val mac = listOf("shaBit")
|
|
||||||
val tid = "00000000"
|
|
||||||
|
|
||||||
// Bob should receive a cancel
|
|
||||||
var canceledToDeviceEvent: Event? = null
|
|
||||||
val cancelLatch = CountDownLatch(1)
|
|
||||||
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
|
||||||
// TODO override fun onToDeviceEvent(event: Event?) {
|
|
||||||
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
|
||||||
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
|
|
||||||
// TODO canceledToDeviceEvent = event
|
|
||||||
// TODO cancelLatch.countDown()
|
|
||||||
// TODO }
|
|
||||||
// TODO }
|
|
||||||
// TODO }
|
|
||||||
// TODO })
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val aliceUserID = aliceSession.myUserId
|
|
||||||
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
|
|
||||||
|
|
||||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
|
|
||||||
|
|
||||||
testHelper.await(cancelLatch)
|
|
||||||
|
|
||||||
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
|
||||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore("This test will be ignored until it is fixed")
|
|
||||||
fun test_key_agreement_short_code_include_decimal() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
|
||||||
fail("Not passing for the moment")
|
|
||||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val bobSession = cryptoTestData.secondSession!!
|
|
||||||
|
|
||||||
val codes = listOf("bin", "foo", "bar")
|
|
||||||
val tid = "00000000"
|
|
||||||
|
|
||||||
// Bob should receive a cancel
|
|
||||||
var canceledToDeviceEvent: Event? = null
|
|
||||||
val cancelLatch = CountDownLatch(1)
|
|
||||||
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
|
||||||
// TODO override fun onToDeviceEvent(event: Event?) {
|
|
||||||
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
|
||||||
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
|
|
||||||
// TODO canceledToDeviceEvent = event
|
|
||||||
// TODO cancelLatch.countDown()
|
|
||||||
// TODO }
|
|
||||||
// TODO }
|
|
||||||
// TODO }
|
|
||||||
// TODO })
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val aliceUserID = aliceSession.myUserId
|
|
||||||
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
|
|
||||||
|
|
||||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
|
|
||||||
|
|
||||||
testHelper.await(cancelLatch)
|
|
||||||
|
|
||||||
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
|
||||||
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fakeBobStart(
|
|
||||||
bobSession: Session,
|
|
||||||
aliceUserID: String?,
|
|
||||||
aliceDevice: String?,
|
|
||||||
tid: String,
|
|
||||||
protocols: List<String> = SASDefaultVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
|
|
||||||
hashes: List<String> = SASDefaultVerificationTransaction.KNOWN_HASHES,
|
|
||||||
mac: List<String> = SASDefaultVerificationTransaction.KNOWN_MACS,
|
|
||||||
codes: List<String> = SASDefaultVerificationTransaction.KNOWN_SHORT_CODES
|
|
||||||
) {
|
|
||||||
val startMessage = KeyVerificationStart(
|
|
||||||
fromDevice = bobSession.cryptoService().getMyDevice().deviceId,
|
|
||||||
method = VerificationMethod.SAS.toValue(),
|
|
||||||
transactionId = tid,
|
|
||||||
keyAgreementProtocols = protocols,
|
|
||||||
hashes = hashes,
|
|
||||||
messageAuthenticationCodes = mac,
|
|
||||||
shortAuthenticationStrings = codes
|
|
||||||
)
|
|
||||||
|
|
||||||
val contentMap = MXUsersDevicesMap<Any>()
|
|
||||||
contentMap.setObject(aliceUserID, aliceDevice, startMessage)
|
|
||||||
|
|
||||||
// TODO val sendLatch = CountDownLatch(1)
|
|
||||||
// TODO bobSession.cryptoRestClient.sendToDevice(
|
|
||||||
// TODO EventType.KEY_VERIFICATION_START,
|
|
||||||
// TODO contentMap,
|
|
||||||
// TODO tid,
|
|
||||||
// TODO TestMatrixCallback<Void>(sendLatch)
|
|
||||||
// TODO )
|
|
||||||
}
|
|
||||||
|
|
||||||
// any two devices may only have at most one key verification in flight at a time.
|
|
||||||
// If a device has two verifications in progress with the same device, then it should cancel both verifications.
|
|
||||||
@Test
|
|
||||||
fun test_aliceStartTwoRequests() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
|
||||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val bobSession = cryptoTestData.secondSession
|
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
|
||||||
|
|
||||||
val aliceCreatedLatch = CountDownLatch(2)
|
|
||||||
val aliceCancelledLatch = CountDownLatch(2)
|
|
||||||
val createdTx = mutableListOf<SASDefaultVerificationTransaction>()
|
|
||||||
val aliceListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionCreated(tx: VerificationTransaction) {
|
|
||||||
createdTx.add(tx as SASDefaultVerificationTransaction)
|
|
||||||
aliceCreatedLatch.countDown()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
if ((tx as SASDefaultVerificationTransaction).state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) {
|
|
||||||
aliceCancelledLatch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aliceVerificationService.addListener(aliceListener)
|
|
||||||
|
|
||||||
val bobUserId = bobSession!!.myUserId
|
|
||||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
|
||||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
|
||||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
|
||||||
|
|
||||||
testHelper.await(aliceCreatedLatch)
|
|
||||||
testHelper.await(aliceCancelledLatch)
|
|
||||||
|
|
||||||
cryptoTestData.cleanUp(testHelper)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that when alice starts a 'correct' request, bob agrees.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@Ignore("This test will be ignored until it is fixed")
|
|
||||||
fun test_aliceAndBobAgreement() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
|
||||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val bobSession = cryptoTestData.secondSession
|
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
|
||||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
|
||||||
|
|
||||||
var accepted: ValidVerificationInfoAccept? = null
|
|
||||||
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
|
|
||||||
|
|
||||||
val aliceAcceptedLatch = CountDownLatch(1)
|
|
||||||
val aliceListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
Log.v("TEST", "== aliceTx state ${tx.state} => ${(tx as? OutgoingSasVerificationTransaction)?.uxState}")
|
|
||||||
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
|
||||||
val at = tx as SASDefaultVerificationTransaction
|
|
||||||
accepted = at.accepted
|
|
||||||
startReq = at.startReq
|
|
||||||
aliceAcceptedLatch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aliceVerificationService.addListener(aliceListener)
|
|
||||||
|
|
||||||
val bobListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
Log.v("TEST", "== bobTx state ${tx.state} => ${(tx as? IncomingSasVerificationTransaction)?.uxState}")
|
|
||||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
|
||||||
bobVerificationService.removeListener(this)
|
|
||||||
val at = tx as IncomingSasVerificationTransaction
|
|
||||||
at.performAccept()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bobVerificationService.addListener(bobListener)
|
|
||||||
|
|
||||||
val bobUserId = bobSession.myUserId
|
|
||||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
|
||||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
|
||||||
testHelper.await(aliceAcceptedLatch)
|
|
||||||
|
|
||||||
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
|
||||||
|
|
||||||
// check that agreement is valid
|
|
||||||
assertTrue("Agreed Protocol should be Valid", accepted != null)
|
|
||||||
assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols.contains(accepted!!.keyAgreementProtocol))
|
|
||||||
assertTrue("Hash should be known by alice", startReq!!.hashes.contains(accepted!!.hash))
|
|
||||||
assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes.contains(accepted!!.messageAuthenticationCode))
|
|
||||||
|
|
||||||
accepted!!.shortAuthenticationStrings.forEach {
|
|
||||||
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_aliceAndBobSASCode() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
|
||||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val bobSession = cryptoTestData.secondSession
|
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
|
||||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
|
||||||
|
|
||||||
val aliceSASLatch = CountDownLatch(1)
|
|
||||||
val aliceListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
|
||||||
when (uxState) {
|
|
||||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
|
||||||
aliceSASLatch.countDown()
|
|
||||||
}
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aliceVerificationService.addListener(aliceListener)
|
|
||||||
|
|
||||||
val bobSASLatch = CountDownLatch(1)
|
|
||||||
val bobListener = object : VerificationService.Listener {
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
|
||||||
when (uxState) {
|
|
||||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
|
||||||
tx.performAccept()
|
|
||||||
}
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) {
|
|
||||||
bobSASLatch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bobVerificationService.addListener(bobListener)
|
|
||||||
|
|
||||||
val bobUserId = bobSession.myUserId
|
|
||||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
|
||||||
val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
|
||||||
testHelper.await(aliceSASLatch)
|
|
||||||
testHelper.await(bobSASLatch)
|
|
||||||
|
|
||||||
val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
|
|
||||||
val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
|
|
||||||
|
|
||||||
assertEquals(
|
|
||||||
"Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
|
|
||||||
bobTx.getShortCodeRepresentation(SasMode.DECIMAL)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_happyPath() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
|
||||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val bobSession = cryptoTestData.secondSession
|
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
|
||||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
|
||||||
|
|
||||||
val aliceSASLatch = CountDownLatch(1)
|
|
||||||
val aliceListener = object : VerificationService.Listener {
|
|
||||||
var matchOnce = true
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
|
|
||||||
Log.v("TEST", "== aliceState ${uxState.name}")
|
|
||||||
when (uxState) {
|
|
||||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
|
||||||
tx.userHasVerifiedShortCode()
|
|
||||||
}
|
|
||||||
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
|
||||||
if (matchOnce) {
|
|
||||||
matchOnce = false
|
|
||||||
aliceSASLatch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aliceVerificationService.addListener(aliceListener)
|
|
||||||
|
|
||||||
val bobSASLatch = CountDownLatch(1)
|
|
||||||
val bobListener = object : VerificationService.Listener {
|
|
||||||
var acceptOnce = true
|
|
||||||
var matchOnce = true
|
|
||||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
|
||||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
|
||||||
Log.v("TEST", "== bobState ${uxState.name}")
|
|
||||||
when (uxState) {
|
|
||||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
|
||||||
if (acceptOnce) {
|
|
||||||
acceptOnce = false
|
|
||||||
tx.performAccept()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
|
||||||
if (matchOnce) {
|
|
||||||
matchOnce = false
|
|
||||||
tx.userHasVerifiedShortCode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
|
||||||
bobSASLatch.countDown()
|
|
||||||
}
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bobVerificationService.addListener(bobListener)
|
|
||||||
|
|
||||||
val bobUserId = bobSession.myUserId
|
|
||||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
|
||||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
|
||||||
testHelper.await(aliceSASLatch)
|
|
||||||
testHelper.await(bobSASLatch)
|
|
||||||
|
|
||||||
// Assert that devices are verified
|
|
||||||
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId)
|
|
||||||
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? =
|
|
||||||
bobSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId)
|
|
||||||
|
|
||||||
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
|
|
||||||
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_ConcurrentStart() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
|
||||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
|
||||||
|
|
||||||
val aliceSession = cryptoTestData.firstSession
|
|
||||||
val bobSession = cryptoTestData.secondSession
|
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
|
||||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
|
||||||
|
|
||||||
val req = aliceVerificationService.requestKeyVerificationInDMs(
|
|
||||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
|
||||||
bobSession.myUserId,
|
|
||||||
cryptoTestData.roomId
|
|
||||||
)
|
|
||||||
|
|
||||||
var requestID: String? = null
|
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
|
||||||
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
|
|
||||||
requestID = prAlicePOV?.transactionId
|
|
||||||
Log.v("TEST", "== alicePOV is $prAlicePOV")
|
|
||||||
prAlicePOV?.transactionId != null && prAlicePOV.localId == req.localId
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.v("TEST", "== requestID is $requestID")
|
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
|
||||||
val prBobPOV = bobVerificationService.getExistingVerificationRequests(aliceSession.myUserId).firstOrNull()
|
|
||||||
Log.v("TEST", "== prBobPOV is $prBobPOV")
|
|
||||||
prBobPOV?.transactionId == requestID
|
|
||||||
}
|
|
||||||
|
|
||||||
bobVerificationService.readyPendingVerification(
|
|
||||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
|
||||||
aliceSession.myUserId,
|
|
||||||
requestID!!
|
|
||||||
)
|
|
||||||
|
|
||||||
// wait for alice to get the ready
|
|
||||||
testHelper.retryPeriodically {
|
|
||||||
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
|
|
||||||
Log.v("TEST", "== prAlicePOV is $prAlicePOV")
|
|
||||||
prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start concurrent!
|
|
||||||
aliceVerificationService.beginKeyVerificationInDMs(
|
|
||||||
VerificationMethod.SAS,
|
|
||||||
requestID!!,
|
|
||||||
cryptoTestData.roomId,
|
|
||||||
bobSession.myUserId,
|
|
||||||
bobSession.sessionParams.deviceId!!
|
|
||||||
)
|
|
||||||
|
|
||||||
bobVerificationService.beginKeyVerificationInDMs(
|
|
||||||
VerificationMethod.SAS,
|
|
||||||
requestID!!,
|
|
||||||
cryptoTestData.roomId,
|
|
||||||
aliceSession.myUserId,
|
|
||||||
aliceSession.sessionParams.deviceId!!
|
|
||||||
)
|
|
||||||
|
|
||||||
// we should reach SHOW SAS on both
|
|
||||||
var alicePovTx: SasVerificationTransaction?
|
|
||||||
var bobPovTx: SasVerificationTransaction?
|
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
|
||||||
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID!!) as? SasVerificationTransaction
|
|
||||||
Log.v("TEST", "== alicePovTx is $alicePovTx")
|
|
||||||
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
|
||||||
}
|
|
||||||
// wait for alice to get the ready
|
|
||||||
testHelper.retryPeriodically {
|
|
||||||
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID!!) as? SasVerificationTransaction
|
|
||||||
Log.v("TEST", "== bobPovTx is $bobPovTx")
|
|
||||||
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.crypto.verification
|
||||||
|
|
||||||
|
import org.amshove.kluent.fail
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
|
import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
|
import org.matrix.android.sdk.common.CryptoTestData
|
||||||
|
|
||||||
|
class SasVerificationTestHelper(private val testHelper: CommonTestHelper) {
|
||||||
|
suspend fun requestVerificationAndWaitForReadyState(cryptoTestData: CryptoTestData, supportedMethods: List<VerificationMethod>): String {
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
|
||||||
|
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
|
val bobVerificationService = bobSession.cryptoService().verificationService()
|
||||||
|
|
||||||
|
val bobUserId = bobSession.myUserId
|
||||||
|
// Step 1: Alice starts a verification request
|
||||||
|
val transactionId = aliceVerificationService.requestKeyVerificationInDMs(
|
||||||
|
supportedMethods, bobUserId, cryptoTestData.roomId
|
||||||
|
)
|
||||||
|
.transactionId
|
||||||
|
|
||||||
|
testHelper.retryWithBackoff(
|
||||||
|
onFail = {
|
||||||
|
fail("bob should see an incoming verification request with id $transactionId")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
val incomingRequest = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, transactionId)
|
||||||
|
if (incomingRequest != null) {
|
||||||
|
bobVerificationService.readyPendingVerification(
|
||||||
|
supportedMethods,
|
||||||
|
aliceSession.myUserId,
|
||||||
|
incomingRequest.transactionId
|
||||||
|
)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for alice to see the ready
|
||||||
|
testHelper.retryWithBackoff(
|
||||||
|
onFail = {
|
||||||
|
fail("Alice request whould be ready $transactionId")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
val pendingRequest = aliceVerificationService.getExistingVerificationRequest(bobUserId, transactionId)
|
||||||
|
pendingRequest?.state == EVerificationState.Ready
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactionId
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun requestSelfKeyAndWaitForReadyState(session1: Session, session2: Session, supportedMethods: List<VerificationMethod>): String {
|
||||||
|
val session1VerificationService = session1.cryptoService().verificationService()
|
||||||
|
val session2VerificationService = session2.cryptoService().verificationService()
|
||||||
|
|
||||||
|
val requestID = session1VerificationService.requestSelfKeyVerification(supportedMethods).transactionId
|
||||||
|
|
||||||
|
val myUserId = session1.myUserId
|
||||||
|
testHelper.retryWithBackoff {
|
||||||
|
val incomingRequest = session2VerificationService.getExistingVerificationRequest(myUserId, requestID)
|
||||||
|
if (incomingRequest != null) {
|
||||||
|
session2VerificationService.readyPendingVerification(
|
||||||
|
supportedMethods,
|
||||||
|
myUserId,
|
||||||
|
incomingRequest.transactionId
|
||||||
|
)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for alice to see the ready
|
||||||
|
testHelper.retryPeriodically {
|
||||||
|
val pendingRequest = session1VerificationService.getExistingVerificationRequest(myUserId, requestID)
|
||||||
|
pendingRequest?.state == EVerificationState.Ready
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestID
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,199 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.crypto.verification
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
|
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
class VerificationTest : InstrumentedTest {
|
||||||
|
|
||||||
|
data class ExpectedResult(
|
||||||
|
val sasIsSupported: Boolean = false,
|
||||||
|
val otherCanScanQrCode: Boolean = false,
|
||||||
|
val otherCanShowQrCode: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sas = listOf(
|
||||||
|
VerificationMethod.SAS
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sasShow = listOf(
|
||||||
|
VerificationMethod.SAS,
|
||||||
|
VerificationMethod.QR_CODE_SHOW
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sasScan = listOf(
|
||||||
|
VerificationMethod.SAS,
|
||||||
|
VerificationMethod.QR_CODE_SCAN
|
||||||
|
)
|
||||||
|
|
||||||
|
private val sasShowScan = listOf(
|
||||||
|
VerificationMethod.SAS,
|
||||||
|
VerificationMethod.QR_CODE_SHOW,
|
||||||
|
VerificationMethod.QR_CODE_SCAN
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_sas_sas() = doTest(
|
||||||
|
sas,
|
||||||
|
sas,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_sas_show() = doTest(
|
||||||
|
sas,
|
||||||
|
sasShow,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_show_sas() = doTest(
|
||||||
|
sasShow,
|
||||||
|
sas,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_sas_scan() = doTest(
|
||||||
|
sas,
|
||||||
|
sasScan,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_scan_sas() = doTest(
|
||||||
|
sasScan,
|
||||||
|
sas,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_scan_scan() = doTest(
|
||||||
|
sasScan,
|
||||||
|
sasScan,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_show_show() = doTest(
|
||||||
|
sasShow,
|
||||||
|
sasShow,
|
||||||
|
ExpectedResult(sasIsSupported = true),
|
||||||
|
ExpectedResult(sasIsSupported = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_show_scan() = doTest(
|
||||||
|
sasShow,
|
||||||
|
sasScan,
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanScanQrCode = true),
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_scan_show() = doTest(
|
||||||
|
sasScan,
|
||||||
|
sasShow,
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true),
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanScanQrCode = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceAndBob_all_all() = doTest(
|
||||||
|
sasShowScan,
|
||||||
|
sasShowScan,
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true),
|
||||||
|
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun doTest(
|
||||||
|
aliceSupportedMethods: List<VerificationMethod>,
|
||||||
|
bobSupportedMethods: List<VerificationMethod>,
|
||||||
|
expectedResultForAlice: ExpectedResult,
|
||||||
|
expectedResultForBob: ExpectedResult
|
||||||
|
) = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||||
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
|
||||||
|
cryptoTestHelper.initializeCrossSigning(aliceSession)
|
||||||
|
cryptoTestHelper.initializeCrossSigning(bobSession)
|
||||||
|
|
||||||
|
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
|
val bobVerificationService = bobSession.cryptoService().verificationService()
|
||||||
|
|
||||||
|
val transactionId = aliceVerificationService.requestKeyVerificationInDMs(
|
||||||
|
aliceSupportedMethods, bobSession.myUserId, cryptoTestData.roomId
|
||||||
|
)
|
||||||
|
.transactionId
|
||||||
|
|
||||||
|
testHelper.retryPeriodically {
|
||||||
|
val incomingRequest = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, transactionId)
|
||||||
|
if (incomingRequest != null) {
|
||||||
|
bobVerificationService.readyPendingVerification(
|
||||||
|
bobSupportedMethods,
|
||||||
|
aliceSession.myUserId,
|
||||||
|
incomingRequest.transactionId
|
||||||
|
)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for alice to see the ready
|
||||||
|
testHelper.retryPeriodically {
|
||||||
|
val pendingRequest = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId, transactionId)
|
||||||
|
pendingRequest?.state == EVerificationState.Ready
|
||||||
|
}
|
||||||
|
|
||||||
|
val aliceReadyPendingVerificationRequest = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId, transactionId)!!
|
||||||
|
val bobReadyPendingVerificationRequest = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, transactionId)!!
|
||||||
|
|
||||||
|
aliceReadyPendingVerificationRequest.let { pr ->
|
||||||
|
pr.isSasSupported shouldBe expectedResultForAlice.sasIsSupported
|
||||||
|
pr.weShouldShowScanOption shouldBe expectedResultForAlice.otherCanShowQrCode
|
||||||
|
pr.weShouldDisplayQRCode shouldBe expectedResultForAlice.otherCanScanQrCode
|
||||||
|
}
|
||||||
|
|
||||||
|
bobReadyPendingVerificationRequest.let { pr ->
|
||||||
|
pr.isSasSupported shouldBe expectedResultForBob.sasIsSupported
|
||||||
|
pr.weShouldShowScanOption shouldBe expectedResultForBob.otherCanShowQrCode
|
||||||
|
pr.weShouldDisplayQRCode shouldBe expectedResultForBob.otherCanScanQrCode
|
||||||
|
}
|
||||||
|
|
||||||
|
cryptoTestData.cleanUp(testHelper)
|
||||||
|
}
|
||||||
|
}
|
@ -1,46 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto.verification.qrcode
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
import org.amshove.kluent.shouldBe
|
|
||||||
import org.amshove.kluent.shouldNotBeEqualTo
|
|
||||||
import org.junit.FixMethodOrder
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.junit.runners.MethodSorters
|
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
|
||||||
class SharedSecretTest : InstrumentedTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSharedSecretLengthCase() {
|
|
||||||
repeat(100) {
|
|
||||||
generateSharedSecretV2().length shouldBe 11
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSharedDiffCase() {
|
|
||||||
val sharedSecret1 = generateSharedSecretV2()
|
|
||||||
val sharedSecret2 = generateSharedSecretV2()
|
|
||||||
|
|
||||||
sharedSecret1 shouldNotBeEqualTo sharedSecret2
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,6 +17,8 @@
|
|||||||
package org.matrix.android.sdk.internal.crypto.verification.qrcode
|
package org.matrix.android.sdk.internal.crypto.verification.qrcode
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.async
|
||||||
import org.amshove.kluent.shouldBe
|
import org.amshove.kluent.shouldBe
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
@ -29,14 +31,13 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
|||||||
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
import org.matrix.android.sdk.api.auth.UserPasswordAuth
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
|
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationEvent
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
||||||
import org.matrix.android.sdk.common.SessionTestParams
|
import org.matrix.android.sdk.common.SessionTestParams
|
||||||
import org.matrix.android.sdk.common.TestConstants
|
import org.matrix.android.sdk.common.TestConstants
|
||||||
import java.util.concurrent.CountDownLatch
|
|
||||||
import kotlin.coroutines.Continuation
|
import kotlin.coroutines.Continuation
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
@ -164,7 +165,6 @@ class VerificationTest : InstrumentedTest {
|
|||||||
val aliceSession = cryptoTestData.firstSession
|
val aliceSession = cryptoTestData.firstSession
|
||||||
val bobSession = cryptoTestData.secondSession!!
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
|
||||||
testHelper.waitForCallback<Unit> { callback ->
|
|
||||||
aliceSession.cryptoService().crossSigningService()
|
aliceSession.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(
|
.initializeCrossSigning(
|
||||||
object : UserInteractiveAuthInterceptor {
|
object : UserInteractiveAuthInterceptor {
|
||||||
@ -177,11 +177,9 @@ class VerificationTest : InstrumentedTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, callback
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
testHelper.waitForCallback<Unit> { callback ->
|
|
||||||
bobSession.cryptoService().crossSigningService()
|
bobSession.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(
|
.initializeCrossSigning(
|
||||||
object : UserInteractiveAuthInterceptor {
|
object : UserInteractiveAuthInterceptor {
|
||||||
@ -194,64 +192,50 @@ class VerificationTest : InstrumentedTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, callback
|
}
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
val bobVerificationService = bobSession.cryptoService().verificationService()
|
val bobVerificationService = bobSession.cryptoService().verificationService()
|
||||||
|
|
||||||
var aliceReadyPendingVerificationRequest: PendingVerificationRequest? = null
|
val transactionId = aliceVerificationService.requestKeyVerificationInDMs(
|
||||||
var bobReadyPendingVerificationRequest: PendingVerificationRequest? = null
|
aliceSupportedMethods, bobSession.myUserId, cryptoTestData.roomId
|
||||||
|
)
|
||||||
|
.transactionId
|
||||||
|
|
||||||
val latch = CountDownLatch(2)
|
testHelper.retryPeriodically {
|
||||||
val aliceListener = object : VerificationService.Listener {
|
val incomingRequest = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, transactionId)
|
||||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
if (incomingRequest != null) {
|
||||||
// Step 4: Alice receive the ready request
|
bobVerificationService.readyPendingVerification(
|
||||||
if (pr.isReady) {
|
|
||||||
aliceReadyPendingVerificationRequest = pr
|
|
||||||
latch.countDown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
aliceVerificationService.addListener(aliceListener)
|
|
||||||
|
|
||||||
val bobListener = object : VerificationService.Listener {
|
|
||||||
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
|
||||||
// Step 2: Bob accepts the verification request
|
|
||||||
bobVerificationService.readyPendingVerificationInDMs(
|
|
||||||
bobSupportedMethods,
|
bobSupportedMethods,
|
||||||
aliceSession.myUserId,
|
aliceSession.myUserId,
|
||||||
cryptoTestData.roomId,
|
incomingRequest.transactionId
|
||||||
pr.transactionId!!
|
|
||||||
)
|
)
|
||||||
}
|
true
|
||||||
|
} else {
|
||||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
false
|
||||||
// Step 3: Bob is ready
|
|
||||||
if (pr.isReady) {
|
|
||||||
bobReadyPendingVerificationRequest = pr
|
|
||||||
latch.countDown()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bobVerificationService.addListener(bobListener)
|
|
||||||
|
|
||||||
val bobUserId = bobSession.myUserId
|
// wait for alice to see the ready
|
||||||
// Step 1: Alice starts a verification request
|
testHelper.retryPeriodically {
|
||||||
aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId)
|
val pendingRequest = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId, transactionId)
|
||||||
testHelper.await(latch)
|
pendingRequest?.state == EVerificationState.Ready
|
||||||
|
|
||||||
aliceReadyPendingVerificationRequest!!.let { pr ->
|
|
||||||
pr.isSasSupported() shouldBe expectedResultForAlice.sasIsSupported
|
|
||||||
pr.otherCanShowQrCode() shouldBe expectedResultForAlice.otherCanShowQrCode
|
|
||||||
pr.otherCanScanQrCode() shouldBe expectedResultForAlice.otherCanScanQrCode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bobReadyPendingVerificationRequest!!.let { pr ->
|
val aliceReadyPendingVerificationRequest = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId, transactionId)!!
|
||||||
pr.isSasSupported() shouldBe expectedResultForBob.sasIsSupported
|
val bobReadyPendingVerificationRequest = bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, transactionId)!!
|
||||||
pr.otherCanShowQrCode() shouldBe expectedResultForBob.otherCanShowQrCode
|
|
||||||
pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode
|
aliceReadyPendingVerificationRequest.let { pr ->
|
||||||
|
pr.isSasSupported shouldBe expectedResultForAlice.sasIsSupported
|
||||||
|
pr.weShouldShowScanOption shouldBe expectedResultForAlice.otherCanShowQrCode
|
||||||
|
pr.weShouldDisplayQRCode shouldBe expectedResultForAlice.otherCanScanQrCode
|
||||||
|
}
|
||||||
|
|
||||||
|
bobReadyPendingVerificationRequest.let { pr ->
|
||||||
|
pr.isSasSupported shouldBe expectedResultForBob.sasIsSupported
|
||||||
|
pr.weShouldShowScanOption shouldBe expectedResultForBob.otherCanShowQrCode
|
||||||
|
pr.weShouldDisplayQRCode shouldBe expectedResultForBob.otherCanScanQrCode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,21 +257,42 @@ class VerificationTest : InstrumentedTest {
|
|||||||
val serviceOfVerifier = aliceSessionThatVerifies.cryptoService().verificationService()
|
val serviceOfVerifier = aliceSessionThatVerifies.cryptoService().verificationService()
|
||||||
val serviceOfUserWhoReceivesCancellation = aliceSessionThatReceivesCanceledEvent.cryptoService().verificationService()
|
val serviceOfUserWhoReceivesCancellation = aliceSessionThatReceivesCanceledEvent.cryptoService().verificationService()
|
||||||
|
|
||||||
serviceOfVerifier.addListener(object : VerificationService.Listener {
|
var job: Job? = null
|
||||||
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
job = async {
|
||||||
// Accept verification request
|
serviceOfVerifier.requestEventFlow().collect {
|
||||||
serviceOfVerifier.readyPendingVerification(
|
when (it) {
|
||||||
verificationMethods,
|
is VerificationEvent.RequestAdded -> {
|
||||||
pr.otherUserId,
|
val pr = it.request
|
||||||
pr.transactionId!!,
|
serviceOfVerifier.readyPendingVerification(
|
||||||
)
|
verificationMethods,
|
||||||
|
pr.otherUserId,
|
||||||
|
pr.transactionId,
|
||||||
|
)
|
||||||
|
job?.cancel()
|
||||||
|
}
|
||||||
|
is VerificationEvent.RequestUpdated,
|
||||||
|
is VerificationEvent.TransactionAdded,
|
||||||
|
is VerificationEvent.TransactionUpdated -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
job.await()
|
||||||
|
// serviceOfVerifier.addListener(object : VerificationService.Listener {
|
||||||
|
// override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||||
|
// // Accept verification request
|
||||||
|
// runBlocking {
|
||||||
|
// serviceOfVerifier.readyPendingVerification(
|
||||||
|
// verificationMethods,
|
||||||
|
// pr.otherUserId,
|
||||||
|
// pr.transactionId!!,
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
serviceOfVerified.requestKeyVerification(
|
serviceOfVerified.requestSelfKeyVerification(
|
||||||
methods = verificationMethods,
|
methods = verificationMethods,
|
||||||
otherUserId = aliceSessionToVerify.myUserId,
|
|
||||||
otherDevices = listOfNotNull(aliceSessionThatVerifies.sessionParams.deviceId, aliceSessionThatReceivesCanceledEvent.sessionParams.deviceId),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryPeriodically {
|
||||||
|
@ -52,9 +52,7 @@ class PreShareKeysTest : InstrumentedTest {
|
|||||||
Log.d("#Test", "Room Key Received from alice $preShareCount")
|
Log.d("#Test", "Room Key Received from alice $preShareCount")
|
||||||
|
|
||||||
// Force presharing of new outbound key
|
// Force presharing of new outbound key
|
||||||
testHelper.waitForCallback<Unit> {
|
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID)
|
||||||
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryPeriodically {
|
||||||
val newKeysCount = bobSession.cryptoService().keysBackupService().getTotalNumbersOfKeys()
|
val newKeysCount = bobSession.cryptoService().keysBackupService().getTotalNumbersOfKeys()
|
||||||
@ -65,7 +63,7 @@ class PreShareKeysTest : InstrumentedTest {
|
|||||||
val aliceOutboundSessionInRoom = aliceCryptoStore.getCurrentOutboundGroupSessionForRoom(e2eRoomID)!!.outboundGroupSession.sessionIdentifier()
|
val aliceOutboundSessionInRoom = aliceCryptoStore.getCurrentOutboundGroupSessionForRoom(e2eRoomID)!!.outboundGroupSession.sessionIdentifier()
|
||||||
|
|
||||||
val bobCryptoStore = (bobSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
|
val bobCryptoStore = (bobSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
|
||||||
val aliceDeviceBobPov = bobCryptoStore.getUserDevice(aliceSession.myUserId, aliceSession.sessionParams.deviceId!!)!!
|
val aliceDeviceBobPov = bobCryptoStore.getUserDevice(aliceSession.myUserId, aliceSession.sessionParams.deviceId)!!
|
||||||
val bobInboundForAlice = bobCryptoStore.getInboundGroupSession(aliceOutboundSessionInRoom, aliceDeviceBobPov.identityKey()!!)
|
val bobInboundForAlice = bobCryptoStore.getInboundGroupSession(aliceOutboundSessionInRoom, aliceDeviceBobPov.identityKey()!!)
|
||||||
assertNotNull("Bob should have received and decrypted a room key event from alice", bobInboundForAlice)
|
assertNotNull("Bob should have received and decrypted a room key event from alice", bobInboundForAlice)
|
||||||
assertEquals("Wrong room", e2eRoomID, bobInboundForAlice!!.roomId)
|
assertEquals("Wrong room", e2eRoomID, bobInboundForAlice!!.roomId)
|
@ -94,7 +94,9 @@ class UnwedgingTest : InstrumentedTest {
|
|||||||
val bobSession = cryptoTestData.secondSession!!
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
|
||||||
val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
|
val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
|
||||||
val olmDevice = (aliceSession.cryptoService() as DefaultCryptoService).olmDeviceForTest
|
|
||||||
|
// bobSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||||
|
// aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||||
|
|
||||||
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
|
||||||
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||||
@ -116,9 +118,9 @@ class UnwedgingTest : InstrumentedTest {
|
|||||||
// - Store the olm session between A&B devices
|
// - Store the olm session between A&B devices
|
||||||
// Let us pickle our session with bob here so we can later unpickle it
|
// Let us pickle our session with bob here so we can later unpickle it
|
||||||
// and wedge our session.
|
// and wedge our session.
|
||||||
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(bobSession.cryptoService().getMyDevice().identityKey()!!)
|
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(bobSession.cryptoService().getMyCryptoDevice().identityKey()!!)
|
||||||
sessionIdsForBob!!.size shouldBeEqualTo 1
|
sessionIdsForBob!!.size shouldBeEqualTo 1
|
||||||
val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), bobSession.cryptoService().getMyDevice().identityKey()!!)!!
|
val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), bobSession.cryptoService().getMyCryptoDevice().identityKey()!!)!!
|
||||||
|
|
||||||
val oldSession = serializeForRealm(olmSession.olmSession)
|
val oldSession = serializeForRealm(olmSession.olmSession)
|
||||||
|
|
||||||
@ -142,9 +144,10 @@ class UnwedgingTest : InstrumentedTest {
|
|||||||
|
|
||||||
aliceCryptoStore.storeSession(
|
aliceCryptoStore.storeSession(
|
||||||
OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!),
|
OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!),
|
||||||
bobSession.cryptoService().getMyDevice().identityKey()!!
|
bobSession.cryptoService().getMyCryptoDevice().identityKey()!!
|
||||||
)
|
)
|
||||||
olmDevice.clearOlmSessionCache()
|
// TODO mmm we can't do that with rust
|
||||||
|
// olmDevice.clearOlmSessionCache()
|
||||||
|
|
||||||
// Force new session, and key share
|
// Force new session, and key share
|
||||||
aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId)
|
aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId)
|
||||||
@ -170,7 +173,6 @@ class UnwedgingTest : InstrumentedTest {
|
|||||||
Assert.assertTrue(messagesReceivedByBob[0].root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
|
Assert.assertTrue(messagesReceivedByBob[0].root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
|
||||||
|
|
||||||
// It's a trick to force key request on fail to decrypt
|
// It's a trick to force key request on fail to decrypt
|
||||||
testHelper.waitForCallback<Unit> {
|
|
||||||
bobSession.cryptoService().crossSigningService()
|
bobSession.cryptoService().crossSigningService()
|
||||||
.initializeCrossSigning(
|
.initializeCrossSigning(
|
||||||
object : UserInteractiveAuthInterceptor {
|
object : UserInteractiveAuthInterceptor {
|
||||||
@ -183,9 +185,7 @@ class UnwedgingTest : InstrumentedTest {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, it
|
})
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until we received back the key
|
// Wait until we received back the key
|
||||||
testHelper.retryPeriodically {
|
testHelper.retryPeriodically {
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import org.amshove.kluent.internal.assertFailsWith
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
@ -30,19 +31,13 @@ import org.junit.runner.RunWith
|
|||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
|
||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
|
||||||
import org.matrix.android.sdk.api.listeners.StepProgressListener
|
import org.matrix.android.sdk.api.listeners.StepProgressListener
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupUtils
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrustSignature
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrustSignature
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
|
||||||
@ -50,7 +45,6 @@ import org.matrix.android.sdk.common.RetryTestRule
|
|||||||
import org.matrix.android.sdk.common.TestConstants
|
import org.matrix.android.sdk.common.TestConstants
|
||||||
import org.matrix.android.sdk.common.waitFor
|
import org.matrix.android.sdk.common.waitFor
|
||||||
import java.security.InvalidParameterException
|
import java.security.InvalidParameterException
|
||||||
import java.util.Collections
|
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
|
|
||||||
@ -83,7 +77,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
// - Check backup keys after having marked one as backed up
|
// - Check backup keys after having marked one as backed up
|
||||||
val session = sessions[0]
|
val session = sessions[0]
|
||||||
|
|
||||||
cryptoStore.markBackupDoneForInboundGroupSessions(Collections.singletonList(session))
|
cryptoStore.markBackupDoneForInboundGroupSessions(listOf(session))
|
||||||
|
|
||||||
assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
|
assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
|
||||||
assertEquals(1, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
|
assertEquals(1, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
|
||||||
@ -118,9 +112,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
|
|
||||||
assertFalse(keysBackup.isEnabled())
|
assertFalse(keysBackup.isEnabled())
|
||||||
|
|
||||||
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> {
|
val megolmBackupCreationInfo = keysBackup.prepareKeysBackupVersion(null, null)
|
||||||
keysBackup.prepareKeysBackupVersion(null, null, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, megolmBackupCreationInfo.algorithm)
|
assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, megolmBackupCreationInfo.algorithm)
|
||||||
assertNotNull(megolmBackupCreationInfo.authData.publicKey)
|
assertNotNull(megolmBackupCreationInfo.authData.publicKey)
|
||||||
@ -144,27 +136,20 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
|
|
||||||
assertFalse(keysBackup.isEnabled())
|
assertFalse(keysBackup.isEnabled())
|
||||||
|
|
||||||
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> {
|
val megolmBackupCreationInfo =
|
||||||
keysBackup.prepareKeysBackupVersion(null, null, it)
|
keysBackup.prepareKeysBackupVersion(null, null)
|
||||||
}
|
|
||||||
|
|
||||||
assertFalse(keysBackup.isEnabled())
|
assertFalse(keysBackup.isEnabled())
|
||||||
|
|
||||||
// Create the version
|
// Create the version
|
||||||
val version = testHelper.waitForCallback<KeysVersion> {
|
val version = keysBackup.createKeysBackupVersion(megolmBackupCreationInfo)
|
||||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backup must be enable now
|
// Backup must be enable now
|
||||||
assertTrue(keysBackup.isEnabled())
|
assertTrue(keysBackup.isEnabled())
|
||||||
|
|
||||||
// Check that it's signed with MSK
|
// Check that it's signed with MSK
|
||||||
val versionResult = testHelper.waitForCallback<KeysVersionResult?> {
|
val versionResult = keysBackup.getVersion(version.version)
|
||||||
keysBackup.getVersion(version.version, it)
|
val trust = keysBackup.getKeysBackupTrust(versionResult!!)
|
||||||
}
|
|
||||||
val trust = testHelper.waitForCallback<KeysBackupVersionTrust> {
|
|
||||||
keysBackup.getKeysBackupTrust(versionResult!!, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals("Should have 2 signatures", 2, trust.signatures.size)
|
assertEquals("Should have 2 signatures", 2, trust.signatures.size)
|
||||||
|
|
||||||
@ -211,7 +196,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
|
|
||||||
assertEquals(2, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
|
assertEquals(2, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
|
||||||
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
|
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
|
||||||
|
|
||||||
val stateObserver = StateObserver(keysBackup, latch, 5)
|
val stateObserver = StateObserver(keysBackup, latch, 5)
|
||||||
|
|
||||||
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||||
@ -256,19 +240,10 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
|
|
||||||
assertEquals(2, nbOfKeys)
|
assertEquals(2, nbOfKeys)
|
||||||
|
|
||||||
var lastBackedUpKeysProgress = 0
|
testHelper.retryPeriodically {
|
||||||
|
keysBackup.getTotalNumbersOfKeys() == keysBackup.getTotalNumbersOfBackedUpKeys()
|
||||||
testHelper.waitForCallback<Unit> {
|
|
||||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
|
||||||
override fun onProgress(progress: Int, total: Int) {
|
|
||||||
assertEquals(nbOfKeys, total)
|
|
||||||
lastBackedUpKeysProgress = progress
|
|
||||||
}
|
|
||||||
}, it)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(nbOfKeys, lastBackedUpKeysProgress)
|
|
||||||
|
|
||||||
val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
|
val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
|
||||||
|
|
||||||
assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
|
assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
|
||||||
@ -305,7 +280,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
assertNotNull(keyBackupData!!.sessionData)
|
assertNotNull(keyBackupData!!.sessionData)
|
||||||
|
|
||||||
// - Check pkDecryptionFromRecoveryKey() is able to create a OlmPkDecryption
|
// - Check pkDecryptionFromRecoveryKey() is able to create a OlmPkDecryption
|
||||||
val decryption = keysBackup.pkDecryptionFromRecoveryKey(keyBackupCreationInfo.recoveryKey)
|
val decryption = keysBackup.pkDecryptionFromRecoveryKey(keyBackupCreationInfo.recoveryKey.toBase58())
|
||||||
assertNotNull(decryption)
|
assertNotNull(decryption)
|
||||||
// - Check decryptKeyBackupData() returns stg
|
// - Check decryptKeyBackupData() returns stg
|
||||||
val sessionData = keysBackup
|
val sessionData = keysBackup
|
||||||
@ -313,7 +288,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
keyBackupData,
|
keyBackupData,
|
||||||
session.safeSessionId!!,
|
session.safeSessionId!!,
|
||||||
cryptoTestData.roomId,
|
cryptoTestData.roomId,
|
||||||
decryption!!
|
keyBackupCreationInfo.recoveryKey
|
||||||
)
|
)
|
||||||
assertNotNull(sessionData)
|
assertNotNull(sessionData)
|
||||||
// - Compare the decrypted megolm key with the original one
|
// - Compare the decrypted megolm key with the original one
|
||||||
@ -335,16 +310,13 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||||
|
|
||||||
// - Restore the e2e backup from the homeserver
|
// - Restore the e2e backup from the homeserver
|
||||||
val importRoomKeysResult = testHelper.waitForCallback<ImportRoomKeysResult> {
|
val importRoomKeysResult = testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null
|
||||||
null,
|
)
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||||
|
|
||||||
@ -401,7 +373,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
// // Request is either sent or unsent
|
// // Request is either sent or unsent
|
||||||
// assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null)
|
// assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null)
|
||||||
//
|
//
|
||||||
// testData.cleanUp(mTestHelper)
|
// testData.cleanUp(testHelper)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -430,13 +402,10 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
||||||
|
|
||||||
// - Trust the backup from the new device
|
// - Trust the backup from the new device
|
||||||
testHelper.waitForCallback<Unit> {
|
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion(
|
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion(
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||||
true,
|
true
|
||||||
it
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for backup state to be ReadyToBackUp
|
// Wait for backup state to be ReadyToBackUp
|
||||||
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||||
@ -446,16 +415,17 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
|
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
|
||||||
|
|
||||||
// - Retrieve the last version from the server
|
// - Retrieve the last version from the server
|
||||||
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> {
|
val keysVersionResult = testData.aliceSession2.cryptoService()
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
|
.keysBackupService()
|
||||||
}.toKeysVersionResult()
|
.getCurrentVersion()!!
|
||||||
|
.toKeysVersionResult()
|
||||||
|
|
||||||
// - It must be the same
|
// - It must be the same
|
||||||
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
|
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
|
||||||
|
|
||||||
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> {
|
val keysBackupVersionTrust = testData.aliceSession2.cryptoService()
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
|
.keysBackupService()
|
||||||
}
|
.getKeysBackupTrust(keysVersionResult)
|
||||||
|
|
||||||
// - It must be trusted and must have 2 signatures now
|
// - It must be trusted and must have 2 signatures now
|
||||||
assertTrue(keysBackupVersionTrust.usable)
|
assertTrue(keysBackupVersionTrust.usable)
|
||||||
@ -490,32 +460,32 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
||||||
|
|
||||||
// - Trust the backup from the new device with the recovery key
|
// - Trust the backup from the new device with the recovery key
|
||||||
testHelper.waitForCallback<Unit> {
|
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
|
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey
|
||||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
)
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for backup state to be ReadyToBackUp
|
// Wait for backup state to be ReadyToBackUp
|
||||||
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||||
|
|
||||||
// - Backup must be enabled on the new device, on the same version
|
// - Backup must be enabled on the new device, on the same version
|
||||||
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version)
|
assertEquals(
|
||||||
|
testData.prepareKeysBackupDataResult.version,
|
||||||
|
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version
|
||||||
|
)
|
||||||
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
|
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
|
||||||
|
|
||||||
// - Retrieve the last version from the server
|
// - Retrieve the last version from the server
|
||||||
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> {
|
val keysVersionResult = testData.aliceSession2.cryptoService().keysBackupService()
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
|
.getCurrentVersion()!!
|
||||||
}.toKeysVersionResult()
|
.toKeysVersionResult()
|
||||||
|
|
||||||
// - It must be the same
|
// - It must be the same
|
||||||
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
|
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
|
||||||
|
|
||||||
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> {
|
val keysBackupVersionTrust = testData.aliceSession2.cryptoService()
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
|
.keysBackupService()
|
||||||
}
|
.getKeysBackupTrust(keysVersionResult)
|
||||||
|
|
||||||
// - It must be trusted and must have 2 signatures now
|
// - It must be trusted and must have 2 signatures now
|
||||||
assertTrue(keysBackupVersionTrust.usable)
|
assertTrue(keysBackupVersionTrust.usable)
|
||||||
@ -548,13 +518,10 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
||||||
|
|
||||||
// - Try to trust the backup from the new device with a wrong recovery key
|
// - Try to trust the backup from the new device with a wrong recovery key
|
||||||
testHelper.waitForCallbackError<Unit> {
|
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
|
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
BackupUtils.recoveryKeyFromPassphrase("Bad recovery key")!!,
|
||||||
"Bad recovery key",
|
)
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// - The new device must still see the previous backup as not trusted
|
// - The new device must still see the previous backup as not trusted
|
||||||
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
|
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
|
||||||
@ -592,13 +559,10 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
||||||
|
|
||||||
// - Trust the backup from the new device with the password
|
// - Trust the backup from the new device with the password
|
||||||
testHelper.waitForCallback<Unit> {
|
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
|
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
password
|
||||||
password,
|
)
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for backup state to be ReadyToBackUp
|
// Wait for backup state to be ReadyToBackUp
|
||||||
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||||
@ -608,16 +572,16 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
|
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
|
||||||
|
|
||||||
// - Retrieve the last version from the server
|
// - Retrieve the last version from the server
|
||||||
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> {
|
val keysVersionResult = testData.aliceSession2.cryptoService().keysBackupService()
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
|
.getCurrentVersion()!!
|
||||||
}.toKeysVersionResult()
|
.toKeysVersionResult()
|
||||||
|
|
||||||
// - It must be the same
|
// - It must be the same
|
||||||
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
|
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
|
||||||
|
|
||||||
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> {
|
val keysBackupVersionTrust = testData.aliceSession2.cryptoService()
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
|
.keysBackupService()
|
||||||
}
|
.getKeysBackupTrust(keysVersionResult)
|
||||||
|
|
||||||
// - It must be trusted and must have 2 signatures now
|
// - It must be trusted and must have 2 signatures now
|
||||||
assertTrue(keysBackupVersionTrust.usable)
|
assertTrue(keysBackupVersionTrust.usable)
|
||||||
@ -653,13 +617,10 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
||||||
|
|
||||||
// - Try to trust the backup from the new device with a wrong password
|
// - Try to trust the backup from the new device with a wrong password
|
||||||
testHelper.waitForCallbackError<Unit> {
|
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
|
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
badPassword,
|
||||||
badPassword,
|
)
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// - The new device must still see the previous backup as not trusted
|
// - The new device must still see the previous backup as not trusted
|
||||||
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
|
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
|
||||||
@ -683,18 +644,15 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
|
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
|
||||||
|
|
||||||
// - Try to restore the e2e backup with a wrong recovery key
|
// - Try to restore the e2e backup with a wrong recovery key
|
||||||
val importRoomKeysResult = testHelper.waitForCallbackError<ImportRoomKeysResult> {
|
assertFailsWith<InvalidParameterException> {
|
||||||
keysBackupService.restoreKeysWithRecoveryKey(
|
keysBackupService.restoreKeysWithRecoveryKey(
|
||||||
keysBackupService.keysBackupVersion!!,
|
keysBackupService.keysBackupVersion!!,
|
||||||
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d",
|
BackupUtils.recoveryKeyFromBase58("EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d")!!,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
it
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue(importRoomKeysResult is InvalidParameterException)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -714,20 +672,17 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
// - Restore the e2e backup with the password
|
// - Restore the e2e backup with the password
|
||||||
val steps = ArrayList<StepProgressListener.Step>()
|
val steps = ArrayList<StepProgressListener.Step>()
|
||||||
|
|
||||||
val importRoomKeysResult = testHelper.waitForCallback<ImportRoomKeysResult> {
|
val importRoomKeysResult = testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(
|
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
password,
|
||||||
password,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
object : StepProgressListener {
|
||||||
object : StepProgressListener {
|
override fun onStepProgress(step: StepProgressListener.Step) {
|
||||||
override fun onStepProgress(step: StepProgressListener.Step) {
|
steps.add(step)
|
||||||
steps.add(step)
|
}
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check steps
|
// Check steps
|
||||||
assertEquals(105, steps.size)
|
assertEquals(105, steps.size)
|
||||||
@ -770,18 +725,15 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
|
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
|
||||||
|
|
||||||
// - Try to restore the e2e backup with a wrong password
|
// - Try to restore the e2e backup with a wrong password
|
||||||
val importRoomKeysResult = testHelper.waitForCallbackError<ImportRoomKeysResult> {
|
assertFailsWith<InvalidParameterException> {
|
||||||
keysBackupService.restoreKeyBackupWithPassword(
|
keysBackupService.restoreKeyBackupWithPassword(
|
||||||
keysBackupService.keysBackupVersion!!,
|
keysBackupService.keysBackupVersion!!,
|
||||||
wrongPassword,
|
wrongPassword,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
it
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue(importRoomKeysResult is InvalidParameterException)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -799,16 +751,13 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||||
|
|
||||||
// - Restore the e2e backup with the recovery key.
|
// - Restore the e2e backup with the recovery key.
|
||||||
val importRoomKeysResult = testHelper.waitForCallback<ImportRoomKeysResult> {
|
val importRoomKeysResult = testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null
|
||||||
null,
|
)
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||||
}
|
}
|
||||||
@ -827,18 +776,15 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
|
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
|
||||||
|
|
||||||
// - Try to restore the e2e backup with a password
|
// - Try to restore the e2e backup with a password
|
||||||
val importRoomKeysResult = testHelper.waitForCallbackError<ImportRoomKeysResult> {
|
val importRoomKeysResult = keysBackupService.restoreKeyBackupWithPassword(
|
||||||
keysBackupService.restoreKeyBackupWithPassword(
|
keysBackupService.keysBackupVersion!!,
|
||||||
keysBackupService.keysBackupVersion!!,
|
"password",
|
||||||
"password",
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
)
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
assertTrue(importRoomKeysResult is IllegalStateException)
|
assertTrue(importRoomKeysResult.importedSessionInfo.size > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -860,14 +806,10 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||||
|
|
||||||
// Get key backup version from the homeserver
|
// Get key backup version from the homeserver
|
||||||
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> {
|
val keysVersionResult = keysBackup.getCurrentVersion()!!.toKeysVersionResult()
|
||||||
keysBackup.getCurrentVersion(it)
|
|
||||||
}.toKeysVersionResult()
|
|
||||||
|
|
||||||
// - Check the returned KeyBackupVersion is trusted
|
// - Check the returned KeyBackupVersion is trusted
|
||||||
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> {
|
val keysBackupVersionTrust = keysBackup.getKeysBackupTrust(keysVersionResult!!)
|
||||||
keysBackup.getKeysBackupTrust(keysVersionResult!!, it)
|
|
||||||
}
|
|
||||||
|
|
||||||
assertNotNull(keysBackupVersionTrust)
|
assertNotNull(keysBackupVersionTrust)
|
||||||
assertTrue(keysBackupVersionTrust.usable)
|
assertTrue(keysBackupVersionTrust.usable)
|
||||||
@ -876,7 +818,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
val signature = keysBackupVersionTrust.signatures[0] as KeysBackupVersionTrustSignature.DeviceSignature
|
val signature = keysBackupVersionTrust.signatures[0] as KeysBackupVersionTrustSignature.DeviceSignature
|
||||||
assertTrue(signature.valid)
|
assertTrue(signature.valid)
|
||||||
assertNotNull(signature.device)
|
assertNotNull(signature.device)
|
||||||
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId)
|
assertEquals(cryptoTestData.firstSession.cryptoService().getMyCryptoDevice().deviceId, signature.deviceId)
|
||||||
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
|
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
|
||||||
|
|
||||||
stateObserver.stopAndCheckStates(null)
|
stateObserver.stopAndCheckStates(null)
|
||||||
@ -944,7 +886,9 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
(cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store.resetBackupMarkers()
|
(cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store.resetBackupMarkers()
|
||||||
|
|
||||||
// - Make alice back up all her keys again
|
// - Make alice back up all her keys again
|
||||||
testHelper.waitForCallbackError<Unit> { keysBackup.backupAllGroupSessions(null, it) }
|
testHelper.retryPeriodically {
|
||||||
|
keysBackup.getTotalNumbersOfKeys() == keysBackup.getTotalNumbersOfBackedUpKeys()
|
||||||
|
}
|
||||||
|
|
||||||
// -> That must fail and her backup state must be WrongBackUpVersion
|
// -> That must fail and her backup state must be WrongBackUpVersion
|
||||||
assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.getState())
|
assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.getState())
|
||||||
@ -980,11 +924,17 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||||
|
|
||||||
// Wait for keys backup to finish by asking again to backup keys.
|
// Wait for keys backup to finish by asking again to backup keys.
|
||||||
testHelper.waitForCallback<Unit> {
|
testHelper.retryPeriodically {
|
||||||
keysBackup.backupAllGroupSessions(null, it)
|
keysBackup.getTotalNumbersOfKeys() == keysBackup.getTotalNumbersOfBackedUpKeys()
|
||||||
}
|
}
|
||||||
|
testHelper.retryPeriodically {
|
||||||
|
keysBackup.getState() == KeysBackupState.ReadyToBackUp
|
||||||
|
}
|
||||||
|
// testHelper.doSync<Unit> {
|
||||||
|
// keysBackup.backupAllGroupSessions(null, it)
|
||||||
|
// }
|
||||||
|
|
||||||
val oldDeviceId = cryptoTestData.firstSession.sessionParams.deviceId!!
|
val oldDeviceId = cryptoTestData.firstSession.sessionParams.deviceId
|
||||||
val oldKeyBackupVersion = keysBackup.currentBackupVersion
|
val oldKeyBackupVersion = keysBackup.currentBackupVersion
|
||||||
val aliceUserId = cryptoTestData.firstSession.myUserId
|
val aliceUserId = cryptoTestData.firstSession.myUserId
|
||||||
|
|
||||||
@ -1005,18 +955,16 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
|
|
||||||
val stateObserver2 = StateObserver(keysBackup2)
|
val stateObserver2 = StateObserver(keysBackup2)
|
||||||
|
|
||||||
testHelper.waitForCallbackError<Unit> { keysBackup2.backupAllGroupSessions(null, it) }
|
testHelper.retryPeriodically {
|
||||||
|
keysBackup2.getTotalNumbersOfKeys() == keysBackup2.getTotalNumbersOfBackedUpKeys()
|
||||||
|
}
|
||||||
|
|
||||||
// Backup state must be NotTrusted
|
// Backup state must be NotTrusted
|
||||||
assertEquals("Backup state must be NotTrusted", KeysBackupState.NotTrusted, keysBackup2.getState())
|
assertEquals("Backup state must be NotTrusted", KeysBackupState.NotTrusted, keysBackup2.getState())
|
||||||
assertFalse("Backup should not be enabled", keysBackup2.isEnabled())
|
assertFalse("Backup should not be enabled", keysBackup2.isEnabled())
|
||||||
|
|
||||||
// - Validate the old device from the new one
|
// - Validate the old device from the new one
|
||||||
aliceSession2.cryptoService().setDeviceVerification(
|
aliceSession2.cryptoService().verificationService().markedLocallyAsManuallyVerified(aliceSession2.myUserId, oldDeviceId)
|
||||||
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
|
||||||
aliceSession2.myUserId,
|
|
||||||
oldDeviceId
|
|
||||||
)
|
|
||||||
|
|
||||||
// -> Backup should automatically enable on the new device
|
// -> Backup should automatically enable on the new device
|
||||||
suspendCancellableCoroutine<Unit> { continuation ->
|
suspendCancellableCoroutine<Unit> { continuation ->
|
||||||
@ -1037,8 +985,13 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
// -> It must use the same backup version
|
// -> It must use the same backup version
|
||||||
assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion)
|
assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion)
|
||||||
|
|
||||||
testHelper.waitForCallback<Unit> {
|
// aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it)
|
||||||
aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it)
|
testHelper.retryPeriodically {
|
||||||
|
keysBackup2.getTotalNumbersOfKeys() == keysBackup2.getTotalNumbersOfBackedUpKeys()
|
||||||
|
}
|
||||||
|
|
||||||
|
testHelper.retryPeriodically {
|
||||||
|
aliceSession2.cryptoService().keysBackupService().getState() == KeysBackupState.ReadyToBackUp
|
||||||
}
|
}
|
||||||
|
|
||||||
// -> It must success
|
// -> It must success
|
||||||
@ -1070,7 +1023,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||||||
assertTrue(keysBackup.isEnabled())
|
assertTrue(keysBackup.isEnabled())
|
||||||
|
|
||||||
// Delete the backup
|
// Delete the backup
|
||||||
testHelper.waitForCallback<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) }
|
keysBackup.deleteBackup(keyBackupCreationInfo.version)
|
||||||
|
|
||||||
// Backup is now disabled
|
// Backup is now disabled
|
||||||
assertFalse(keysBackup.isEnabled())
|
assertFalse(keysBackup.isEnabled())
|
@ -0,0 +1,613 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.crypto.verification
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import org.amshove.kluent.internal.assertEquals
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.SasTransactionState
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationEvent
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
|
||||||
|
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
|
class SASTest : InstrumentedTest {
|
||||||
|
|
||||||
|
val scope = CoroutineScope(SupervisorJob())
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_aliceStartThenAliceCancel() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||||
|
|
||||||
|
Timber.v("verification: doE2ETestWithAliceAndBobInARoom")
|
||||||
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
Timber.v("verification: initializeCrossSigning")
|
||||||
|
cryptoTestData.initializeCrossSigning(cryptoTestHelper)
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val bobSession = cryptoTestData.secondSession
|
||||||
|
|
||||||
|
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
|
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||||
|
|
||||||
|
Timber.v("verification: requestVerificationAndWaitForReadyState")
|
||||||
|
val txId = SasVerificationTestHelper(testHelper)
|
||||||
|
.requestVerificationAndWaitForReadyState(cryptoTestData, listOf(VerificationMethod.SAS))
|
||||||
|
|
||||||
|
Timber.v("verification: startKeyVerification")
|
||||||
|
aliceVerificationService.startKeyVerification(
|
||||||
|
VerificationMethod.SAS,
|
||||||
|
bobSession.myUserId,
|
||||||
|
txId
|
||||||
|
)
|
||||||
|
|
||||||
|
Timber.v("verification: ensure bob has received starete")
|
||||||
|
testHelper.retryWithBackoff {
|
||||||
|
bobVerificationService.getExistingVerificationRequest(aliceSession.myUserId, txId)?.state == EVerificationState.Started
|
||||||
|
}
|
||||||
|
|
||||||
|
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txId)
|
||||||
|
|
||||||
|
assertNotNull("Bob should have started verif transaction", bobKeyTx)
|
||||||
|
assertTrue(bobKeyTx is SasVerificationTransaction)
|
||||||
|
|
||||||
|
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txId)
|
||||||
|
assertTrue(aliceKeyTx is SasVerificationTransaction)
|
||||||
|
|
||||||
|
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
|
||||||
|
|
||||||
|
val aliceCancelled = CompletableDeferred<Unit>()
|
||||||
|
aliceVerificationService.requestEventFlow().onEach {
|
||||||
|
println("alice flow event $it")
|
||||||
|
if (it is VerificationEvent.TransactionUpdated && it.transactionId == txId) {
|
||||||
|
val sasVerificationTransaction = it.transaction as SasVerificationTransaction
|
||||||
|
if (sasVerificationTransaction.state() is SasTransactionState.Cancelled) {
|
||||||
|
aliceCancelled.complete(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.launchIn(scope)
|
||||||
|
|
||||||
|
val bobCancelled = CompletableDeferred<Unit>()
|
||||||
|
bobVerificationService.requestEventFlow().onEach {
|
||||||
|
println("alice flow event $it")
|
||||||
|
if (it is VerificationEvent.TransactionUpdated && it.transactionId == txId) {
|
||||||
|
val sasVerificationTransaction = it.transaction as SasVerificationTransaction
|
||||||
|
if (sasVerificationTransaction.state() is SasTransactionState.Cancelled) {
|
||||||
|
bobCancelled.complete(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.launchIn(scope)
|
||||||
|
|
||||||
|
aliceVerificationService.cancelVerificationRequest(bobSession.myUserId, txId)
|
||||||
|
|
||||||
|
aliceCancelled.await()
|
||||||
|
bobCancelled.await()
|
||||||
|
|
||||||
|
val cancelledAlice = aliceVerificationService.getExistingVerificationRequest(bobSession.myUserId, txId)!!
|
||||||
|
val cancelledBob = aliceVerificationService.getExistingVerificationRequest(aliceSession.myUserId, txId)!!
|
||||||
|
|
||||||
|
assertEquals("Should be cancelled on alice side", cancelledAlice.state, EVerificationState.Cancelled)
|
||||||
|
assertEquals("Should be cancelled on alice side", cancelledBob.state, EVerificationState.Cancelled)
|
||||||
|
|
||||||
|
assertEquals("Should be User cancelled on alice side", CancelCode.User, cancelledAlice.cancelConclusion)
|
||||||
|
assertEquals("Should be User cancelled on bob side", CancelCode.User, cancelledBob.cancelConclusion)
|
||||||
|
|
||||||
|
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txId))
|
||||||
|
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txId))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
@Test
|
||||||
|
@Ignore("This test will be ignored until it is fixed")
|
||||||
|
fun test_key_agreement_protocols_must_include_curve25519() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||||
|
fail("Not passing for the moment")
|
||||||
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
|
||||||
|
val protocols = listOf("meh_dont_know")
|
||||||
|
val tid = "00000000"
|
||||||
|
|
||||||
|
// Bob should receive a cancel
|
||||||
|
var cancelReason: CancelCode? = null
|
||||||
|
val cancelLatch = CountDownLatch(1)
|
||||||
|
|
||||||
|
val bobListener = object : VerificationService.Listener {
|
||||||
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
tx as SasVerificationTransaction
|
||||||
|
if (tx.transactionId == tid && tx.state() is SasTransactionState.Cancelled) {
|
||||||
|
cancelReason = (tx.state() as SasTransactionState.Cancelled).cancelCode
|
||||||
|
cancelLatch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// bobSession.cryptoService().verificationService().addListener(bobListener)
|
||||||
|
|
||||||
|
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
||||||
|
// TODO override fun onToDeviceEvent(event: Event?) {
|
||||||
|
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
||||||
|
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
|
||||||
|
// TODO canceledToDeviceEvent = event
|
||||||
|
// TODO cancelLatch.countDown()
|
||||||
|
// TODO }
|
||||||
|
// TODO }
|
||||||
|
// TODO }
|
||||||
|
// TODO })
|
||||||
|
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val aliceUserID = aliceSession.myUserId
|
||||||
|
val aliceDevice = aliceSession.cryptoService().getMyCryptoDevice().deviceId
|
||||||
|
|
||||||
|
val aliceListener = object : VerificationService.Listener {
|
||||||
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
tx as SasVerificationTransaction
|
||||||
|
if (tx.state() is SasTransactionState.SasStarted) {
|
||||||
|
runBlocking {
|
||||||
|
tx.acceptVerification()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// aliceSession.cryptoService().verificationService().addListener(aliceListener)
|
||||||
|
|
||||||
|
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
|
||||||
|
|
||||||
|
testHelper.await(cancelLatch)
|
||||||
|
|
||||||
|
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod, cancelReason)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("This test will be ignored until it is fixed")
|
||||||
|
fun test_key_agreement_macs_Must_include_hmac_sha256() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||||
|
fail("Not passing for the moment")
|
||||||
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
|
||||||
|
val mac = listOf("shaBit")
|
||||||
|
val tid = "00000000"
|
||||||
|
|
||||||
|
// Bob should receive a cancel
|
||||||
|
val canceledToDeviceEvent: Event? = null
|
||||||
|
val cancelLatch = CountDownLatch(1)
|
||||||
|
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
||||||
|
// TODO override fun onToDeviceEvent(event: Event?) {
|
||||||
|
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
||||||
|
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
|
||||||
|
// TODO canceledToDeviceEvent = event
|
||||||
|
// TODO cancelLatch.countDown()
|
||||||
|
// TODO }
|
||||||
|
// TODO }
|
||||||
|
// TODO }
|
||||||
|
// TODO })
|
||||||
|
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val aliceUserID = aliceSession.myUserId
|
||||||
|
val aliceDevice = aliceSession.cryptoService().getMyCryptoDevice().deviceId
|
||||||
|
|
||||||
|
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
|
||||||
|
|
||||||
|
testHelper.await(cancelLatch)
|
||||||
|
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
||||||
|
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("This test will be ignored until it is fixed")
|
||||||
|
fun test_key_agreement_short_code_include_decimal() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||||
|
fail("Not passing for the moment")
|
||||||
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
|
||||||
|
val codes = listOf("bin", "foo", "bar")
|
||||||
|
val tid = "00000000"
|
||||||
|
|
||||||
|
// Bob should receive a cancel
|
||||||
|
var canceledToDeviceEvent: Event? = null
|
||||||
|
val cancelLatch = CountDownLatch(1)
|
||||||
|
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
||||||
|
// TODO override fun onToDeviceEvent(event: Event?) {
|
||||||
|
// TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) {
|
||||||
|
// TODO if (event.contentAsJsonObject?.get("transaction_id")?.asString == tid) {
|
||||||
|
// TODO canceledToDeviceEvent = event
|
||||||
|
// TODO cancelLatch.countDown()
|
||||||
|
// TODO }
|
||||||
|
// TODO }
|
||||||
|
// TODO }
|
||||||
|
// TODO })
|
||||||
|
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val aliceUserID = aliceSession.myUserId
|
||||||
|
val aliceDevice = aliceSession.cryptoService().getMyCryptoDevice().deviceId
|
||||||
|
|
||||||
|
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
|
||||||
|
|
||||||
|
testHelper.await(cancelLatch)
|
||||||
|
|
||||||
|
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
||||||
|
assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fakeBobStart(
|
||||||
|
bobSession: Session,
|
||||||
|
aliceUserID: String?,
|
||||||
|
aliceDevice: String?,
|
||||||
|
tid: String,
|
||||||
|
protocols: List<String> = SasVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
|
||||||
|
hashes: List<String> = SasVerificationTransaction.KNOWN_HASHES,
|
||||||
|
mac: List<String> = SasVerificationTransaction.KNOWN_MACS,
|
||||||
|
codes: List<String> = SasVerificationTransaction.KNOWN_SHORT_CODES
|
||||||
|
) {
|
||||||
|
val startMessage = KeyVerificationStart(
|
||||||
|
fromDevice = bobSession.cryptoService().getMyCryptoDevice().deviceId,
|
||||||
|
method = VerificationMethod.SAS.toValue(),
|
||||||
|
transactionId = tid,
|
||||||
|
keyAgreementProtocols = protocols,
|
||||||
|
hashes = hashes,
|
||||||
|
messageAuthenticationCodes = mac,
|
||||||
|
shortAuthenticationStrings = codes
|
||||||
|
)
|
||||||
|
|
||||||
|
val contentMap = MXUsersDevicesMap<Any>()
|
||||||
|
contentMap.setObject(aliceUserID, aliceDevice, startMessage)
|
||||||
|
|
||||||
|
// TODO val sendLatch = CountDownLatch(1)
|
||||||
|
// TODO bobSession.cryptoRestClient.sendToDevice(
|
||||||
|
// TODO EventType.KEY_VERIFICATION_START,
|
||||||
|
// TODO contentMap,
|
||||||
|
// TODO tid,
|
||||||
|
// TODO TestMatrixCallback<Void>(sendLatch)
|
||||||
|
// TODO )
|
||||||
|
}
|
||||||
|
|
||||||
|
// any two devices may only have at most one key verification in flight at a time.
|
||||||
|
// If a device has two verifications in progress with the same device, then it should cancel both verifications.
|
||||||
|
@Test
|
||||||
|
fun test_aliceStartTwoRequests() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||||
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val bobSession = cryptoTestData.secondSession
|
||||||
|
|
||||||
|
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
|
|
||||||
|
val aliceCreatedLatch = CountDownLatch(2)
|
||||||
|
val aliceCancelledLatch = CountDownLatch(1)
|
||||||
|
val createdTx = mutableListOf<VerificationTransaction>()
|
||||||
|
val aliceListener = object : VerificationService.Listener {
|
||||||
|
override fun transactionCreated(tx: VerificationTransaction) {
|
||||||
|
createdTx.add(tx)
|
||||||
|
aliceCreatedLatch.countDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
tx as SasVerificationTransaction
|
||||||
|
if (tx.state() is SasTransactionState.Cancelled && !(tx.state() as SasTransactionState.Cancelled).byMe) {
|
||||||
|
aliceCancelledLatch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
|
val bobUserId = bobSession!!.myUserId
|
||||||
|
val bobDeviceId = bobSession.cryptoService().getMyCryptoDevice().deviceId
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), forceDownload = true)
|
||||||
|
// aliceVerificationService.beginKeyVerification(listOf(VerificationMethod.SAS), bobUserId, bobDeviceId)
|
||||||
|
// aliceVerificationService.beginKeyVerification(bobUserId, bobDeviceId)
|
||||||
|
// testHelper.await(aliceCreatedLatch)
|
||||||
|
// testHelper.await(aliceCancelledLatch)
|
||||||
|
|
||||||
|
cryptoTestData.cleanUp(testHelper)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that when alice starts a 'correct' request, bob agrees.
|
||||||
|
*/
|
||||||
|
// @Test
|
||||||
|
// @Ignore("This test will be ignored until it is fixed")
|
||||||
|
// fun test_aliceAndBobAgreement() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||||
|
// val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
//
|
||||||
|
// val aliceSession = cryptoTestData.firstSession
|
||||||
|
// val bobSession = cryptoTestData.secondSession
|
||||||
|
//
|
||||||
|
// val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
|
// val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||||
|
//
|
||||||
|
// val aliceAcceptedLatch = CountDownLatch(1)
|
||||||
|
// val aliceListener = object : VerificationService.Listener {
|
||||||
|
// override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
// if (tx.state() is VerificationTxState.OnAccepted) {
|
||||||
|
// aliceAcceptedLatch.countDown()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// aliceVerificationService.addListener(aliceListener)
|
||||||
|
//
|
||||||
|
// val bobListener = object : VerificationService.Listener {
|
||||||
|
// override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
// if (tx.state() is VerificationTxState.OnStarted && tx is SasVerificationTransaction) {
|
||||||
|
// bobVerificationService.removeListener(this)
|
||||||
|
// runBlocking {
|
||||||
|
// tx.acceptVerification()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// bobVerificationService.addListener(bobListener)
|
||||||
|
//
|
||||||
|
// val bobUserId = bobSession.myUserId
|
||||||
|
// val bobDeviceId = runBlocking {
|
||||||
|
// bobSession.cryptoService().getMyCryptoDevice().deviceId
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||||
|
// testHelper.await(aliceAcceptedLatch)
|
||||||
|
//
|
||||||
|
// aliceVerificationService.getExistingTransaction(bobUserId, )
|
||||||
|
//
|
||||||
|
// assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
|
||||||
|
//
|
||||||
|
// // check that agreement is valid
|
||||||
|
// assertTrue("Agreed Protocol should be Valid", accepted != null)
|
||||||
|
// assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols.contains(accepted!!.keyAgreementProtocol))
|
||||||
|
// assertTrue("Hash should be known by alice", startReq!!.hashes.contains(accepted!!.hash))
|
||||||
|
// assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes.contains(accepted!!.messageAuthenticationCode))
|
||||||
|
//
|
||||||
|
// accepted!!.shortAuthenticationStrings.forEach {
|
||||||
|
// assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @Test
|
||||||
|
// fun test_aliceAndBobSASCode() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||||
|
// val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
// cryptoTestData.initializeCrossSigning(cryptoTestHelper)
|
||||||
|
// val sasTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
|
||||||
|
// val aliceSession = cryptoTestData.firstSession
|
||||||
|
// val bobSession = cryptoTestData.secondSession!!
|
||||||
|
// val transactionId = sasTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, supportedMethods)
|
||||||
|
//
|
||||||
|
// val latch = CountDownLatch(2)
|
||||||
|
// val aliceListener = object : VerificationService.Listener {
|
||||||
|
// override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
// Timber.v("Alice transactionUpdated: ${tx.state()}")
|
||||||
|
// latch.countDown()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// aliceSession.cryptoService().verificationService().addListener(aliceListener)
|
||||||
|
// val bobListener = object : VerificationService.Listener {
|
||||||
|
// override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
// Timber.v("Bob transactionUpdated: ${tx.state()}")
|
||||||
|
// latch.countDown()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// bobSession.cryptoService().verificationService().addListener(bobListener)
|
||||||
|
// aliceSession.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, transactionId)
|
||||||
|
//
|
||||||
|
// testHelper.await(latch)
|
||||||
|
// val aliceTx =
|
||||||
|
// aliceSession.cryptoService().verificationService().getExistingTransaction(bobSession.myUserId, transactionId) as SasVerificationTransaction
|
||||||
|
// val bobTx = bobSession.cryptoService().verificationService().getExistingTransaction(aliceSession.myUserId, transactionId) as SasVerificationTransaction
|
||||||
|
//
|
||||||
|
// assertEquals("Should have same SAS", aliceTx.getDecimalCodeRepresentation(), bobTx.getDecimalCodeRepresentation())
|
||||||
|
//
|
||||||
|
// val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
|
||||||
|
// val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
|
||||||
|
//
|
||||||
|
// assertEquals(
|
||||||
|
// "Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
|
||||||
|
// bobTx.getShortCodeRepresentation(SasMode.DECIMAL)
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_happyPath() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||||
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
cryptoTestData.initializeCrossSigning(cryptoTestHelper)
|
||||||
|
val sasVerificationTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
|
||||||
|
val transactionId = sasVerificationTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, listOf(VerificationMethod.SAS))
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val bobSession = cryptoTestData.secondSession
|
||||||
|
|
||||||
|
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
|
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||||
|
|
||||||
|
val verifiedLatch = CountDownLatch(2)
|
||||||
|
val aliceListener = object : VerificationService.Listener {
|
||||||
|
|
||||||
|
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||||
|
Timber.v("RequestUpdated pr=$pr")
|
||||||
|
}
|
||||||
|
|
||||||
|
var matched = false
|
||||||
|
var verified = false
|
||||||
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
if (tx !is SasVerificationTransaction) return
|
||||||
|
Timber.v("Alice transactionUpdated: ${tx.state()} on thread:${Thread.currentThread()}")
|
||||||
|
when (tx.state()) {
|
||||||
|
SasTransactionState.SasShortCodeReady -> {
|
||||||
|
if (!matched) {
|
||||||
|
matched = true
|
||||||
|
runBlocking {
|
||||||
|
delay(500)
|
||||||
|
tx.userHasVerifiedShortCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is SasTransactionState.Done -> {
|
||||||
|
if (!verified) {
|
||||||
|
verified = true
|
||||||
|
verifiedLatch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// aliceVerificationService.addListener(aliceListener)
|
||||||
|
|
||||||
|
val bobListener = object : VerificationService.Listener {
|
||||||
|
var accepted = false
|
||||||
|
var matched = false
|
||||||
|
var verified = false
|
||||||
|
|
||||||
|
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||||
|
Timber.v("RequestUpdated: pr=$pr")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||||
|
if (tx !is SasVerificationTransaction) return
|
||||||
|
Timber.v("Bob transactionUpdated: ${tx.state()} on thread: ${Thread.currentThread()}")
|
||||||
|
when (tx.state()) {
|
||||||
|
// VerificationTxState.SasStarted -> {
|
||||||
|
// if (!accepted) {
|
||||||
|
// accepted = true
|
||||||
|
// runBlocking {
|
||||||
|
// tx.acceptVerification()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
SasTransactionState.SasShortCodeReady -> {
|
||||||
|
if (!matched) {
|
||||||
|
matched = true
|
||||||
|
runBlocking {
|
||||||
|
delay(500)
|
||||||
|
tx.userHasVerifiedShortCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is SasTransactionState.Done -> {
|
||||||
|
if (!verified) {
|
||||||
|
verified = true
|
||||||
|
verifiedLatch.countDown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// bobVerificationService.addListener(bobListener)
|
||||||
|
|
||||||
|
val bobUserId = bobSession.myUserId
|
||||||
|
val bobDeviceId = runBlocking {
|
||||||
|
bobSession.cryptoService().getMyCryptoDevice().deviceId
|
||||||
|
}
|
||||||
|
aliceVerificationService.startKeyVerification(VerificationMethod.SAS, bobUserId, transactionId)
|
||||||
|
|
||||||
|
Timber.v("Await after beginKey ${Thread.currentThread()}")
|
||||||
|
testHelper.await(verifiedLatch)
|
||||||
|
|
||||||
|
// Assert that devices are verified
|
||||||
|
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId)
|
||||||
|
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? =
|
||||||
|
bobSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyCryptoDevice().deviceId)
|
||||||
|
|
||||||
|
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
|
||||||
|
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun test_ConcurrentStart() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||||
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||||
|
cryptoTestData.initializeCrossSigning(cryptoTestHelper)
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val bobSession = cryptoTestData.secondSession!!
|
||||||
|
|
||||||
|
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||||
|
val bobVerificationService = bobSession.cryptoService().verificationService()
|
||||||
|
|
||||||
|
val req = aliceVerificationService.requestKeyVerificationInDMs(
|
||||||
|
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||||
|
bobSession.myUserId,
|
||||||
|
cryptoTestData.roomId
|
||||||
|
)
|
||||||
|
|
||||||
|
val requestID = req.transactionId
|
||||||
|
|
||||||
|
Log.v("TEST", "== requestID is $requestID")
|
||||||
|
|
||||||
|
testHelper.retryPeriodically {
|
||||||
|
val prBobPOV = bobVerificationService.getExistingVerificationRequests(aliceSession.myUserId).firstOrNull()
|
||||||
|
Log.v("TEST", "== prBobPOV is $prBobPOV")
|
||||||
|
prBobPOV?.transactionId == requestID
|
||||||
|
}
|
||||||
|
|
||||||
|
bobVerificationService.readyPendingVerification(
|
||||||
|
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||||
|
aliceSession.myUserId,
|
||||||
|
requestID
|
||||||
|
)
|
||||||
|
|
||||||
|
// wait for alice to get the ready
|
||||||
|
testHelper.retryPeriodically {
|
||||||
|
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
|
||||||
|
Log.v("TEST", "== prAlicePOV is $prAlicePOV")
|
||||||
|
prAlicePOV?.transactionId == requestID && prAlicePOV.state == EVerificationState.Ready
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start concurrent!
|
||||||
|
aliceVerificationService.startKeyVerification(
|
||||||
|
method = VerificationMethod.SAS,
|
||||||
|
otherUserId = bobSession.myUserId,
|
||||||
|
requestId = requestID,
|
||||||
|
)
|
||||||
|
|
||||||
|
bobVerificationService.startKeyVerification(
|
||||||
|
method = VerificationMethod.SAS,
|
||||||
|
otherUserId = aliceSession.myUserId,
|
||||||
|
requestId = requestID,
|
||||||
|
)
|
||||||
|
|
||||||
|
// we should reach SHOW SAS on both
|
||||||
|
var alicePovTx: SasVerificationTransaction?
|
||||||
|
var bobPovTx: SasVerificationTransaction?
|
||||||
|
|
||||||
|
testHelper.retryPeriodically {
|
||||||
|
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID) as? SasVerificationTransaction
|
||||||
|
Log.v("TEST", "== alicePovTx is $alicePovTx")
|
||||||
|
alicePovTx?.state() == SasTransactionState.SasShortCodeReady
|
||||||
|
}
|
||||||
|
// wait for alice to get the ready
|
||||||
|
testHelper.retryPeriodically {
|
||||||
|
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID) as? SasVerificationTransaction
|
||||||
|
Log.v("TEST", "== bobPovTx is $bobPovTx")
|
||||||
|
bobPovTx?.state() == SasTransactionState.SasShortCodeReady
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.crypto.store.migration
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
import org.amshove.kluent.internal.assertEquals
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntity
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntity
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.TestRealmConfigurationFactory
|
||||||
|
import org.matrix.android.sdk.internal.session.MigrateEAtoEROperation
|
||||||
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
|
import org.matrix.olm.OlmAccount
|
||||||
|
import org.matrix.olm.OlmManager
|
||||||
|
import org.matrix.rustcomponents.sdk.crypto.OlmMachine
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ElementAndroidToElementRMigrationTest : InstrumentedTest {
|
||||||
|
|
||||||
|
@get:Rule val configurationFactory = TestRealmConfigurationFactory()
|
||||||
|
|
||||||
|
lateinit var context: Context
|
||||||
|
var realm: Realm? = null
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
// Ensure Olm is initialized
|
||||||
|
OlmManager()
|
||||||
|
context = InstrumentationRegistry.getInstrumentation().context
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
realm?.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun given_a_valid_crypto_store_realm_file_then_migration_should_be_successful() {
|
||||||
|
val realmName = "crypto_store_migration_16.realm"
|
||||||
|
val migration = RealmCryptoStoreMigration(object : Clock {
|
||||||
|
override fun epochMillis() = 0L
|
||||||
|
})
|
||||||
|
|
||||||
|
val realmConfiguration = configurationFactory.createConfiguration(
|
||||||
|
realmName,
|
||||||
|
null,
|
||||||
|
RealmCryptoStoreModule(),
|
||||||
|
migration.schemaVersion,
|
||||||
|
migration
|
||||||
|
)
|
||||||
|
configurationFactory.copyRealmFromAssets(context, realmName, realmName)
|
||||||
|
|
||||||
|
realm = Realm.getInstance(realmConfiguration)
|
||||||
|
|
||||||
|
val metaData = realm!!.where<CryptoMetadataEntity>().findFirst()!!
|
||||||
|
val userId = metaData.userId!!
|
||||||
|
val deviceId = metaData.deviceId!!
|
||||||
|
val olmAccount = metaData.getOlmAccount()!!
|
||||||
|
|
||||||
|
val extractor = MigrateEAtoEROperation()
|
||||||
|
|
||||||
|
val targetFile = File(configurationFactory.root, "rust-sdk")
|
||||||
|
|
||||||
|
extractor.execute(realmConfiguration, targetFile)
|
||||||
|
|
||||||
|
val machine = OlmMachine(userId, deviceId, targetFile.path, null)
|
||||||
|
|
||||||
|
assertEquals(olmAccount.identityKeys()[OlmAccount.JSON_KEY_FINGER_PRINT_KEY], machine.identityKeys()["ed25519"])
|
||||||
|
assertNotNull(machine.getBackupKeys())
|
||||||
|
val crossSigningStatus = machine.crossSigningStatus()
|
||||||
|
assertTrue(crossSigningStatus.hasMaster)
|
||||||
|
assertTrue(crossSigningStatus.hasSelfSigning)
|
||||||
|
assertTrue(crossSigningStatus.hasUserSigning)
|
||||||
|
|
||||||
|
val inboundGroupSessionEntities = realm!!.where<OlmInboundGroupSessionEntity>().findAll()
|
||||||
|
assertEquals(inboundGroupSessionEntities.size, machine.roomKeyCounts().total.toInt())
|
||||||
|
|
||||||
|
val backedUpInboundGroupSessionEntities = realm!!
|
||||||
|
.where<OlmInboundGroupSessionEntity>()
|
||||||
|
.equalTo(OlmInboundGroupSessionEntityFields.BACKED_UP, true)
|
||||||
|
.findAll()
|
||||||
|
assertEquals(backedUpInboundGroupSessionEntities.size, machine.roomKeyCounts().backedUp.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Test
|
||||||
|
// fun given_an_empty_crypto_store_realm_file_then_migration_should_not_happen() {
|
||||||
|
// val realmConfiguration = realmConfigurationFactory.configurationForMigrationFrom15To16(populateCryptoStore = false)
|
||||||
|
// Realm.getInstance(realmConfiguration).use {
|
||||||
|
// assertTrue(it.isEmpty)
|
||||||
|
// }
|
||||||
|
// val machine = OlmMachine("@ganfra146:matrix.org", "UTDQCHKKNS", realmConfigurationFactory.root.path, null)
|
||||||
|
// assertNull(machine.getBackupKeys())
|
||||||
|
// val crossSigningStatus = machine.crossSigningStatus()
|
||||||
|
// assertFalse(crossSigningStatus.hasMaster)
|
||||||
|
// assertFalse(crossSigningStatus.hasSelfSigning)
|
||||||
|
// assertFalse(crossSigningStatus.hasUserSigning)
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.api.session.crypto.keysbackup
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.util.toBase64NoPadding
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tools.withOlmDecryption
|
||||||
|
import org.matrix.olm.OlmPkMessage
|
||||||
|
|
||||||
|
class BackupRecoveryKey(private val key: ByteArray) : IBackupRecoveryKey {
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is BackupRecoveryKey) return false
|
||||||
|
return this.toBase58() == other.toBase58()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toBase58() = computeRecoveryKey(key)
|
||||||
|
|
||||||
|
override fun toBase64() = key.toBase64NoPadding()
|
||||||
|
|
||||||
|
override fun decryptV1(ephemeralKey: String, mac: String, ciphertext: String): String = withOlmDecryption {
|
||||||
|
it.setPrivateKey(key)
|
||||||
|
it.decrypt(OlmPkMessage().apply {
|
||||||
|
this.mEphemeralKey = ephemeralKey
|
||||||
|
this.mCipherText = ciphertext
|
||||||
|
this.mMac = mac
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun megolmV1PublicKey() = v1pk
|
||||||
|
|
||||||
|
private val v1pk = object : IMegolmV1PublicKey {
|
||||||
|
override val publicKey: String
|
||||||
|
get() = withOlmDecryption {
|
||||||
|
it.setPrivateKey(key)
|
||||||
|
}
|
||||||
|
override val privateKeySalt: String?
|
||||||
|
get() = null // not use in kotlin sdk
|
||||||
|
override val privateKeyIterations: Int?
|
||||||
|
get() = null // not use in kotlin sdk
|
||||||
|
override val backupAlgorithm: String
|
||||||
|
get() = "" // not use in kotlin sdk
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.api.session.crypto.keysbackup
|
||||||
|
|
||||||
|
object BackupUtils {
|
||||||
|
|
||||||
|
fun recoveryKeyFromBase58(base58: String): IBackupRecoveryKey? {
|
||||||
|
return extractCurveKeyFromRecoveryKey(base58)?.let {
|
||||||
|
BackupRecoveryKey(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun recoveryKeyFromPassphrase(passphrase: String): IBackupRecoveryKey? {
|
||||||
|
return extractCurveKeyFromRecoveryKey(passphrase)?.let {
|
||||||
|
BackupRecoveryKey(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@
|
|||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
@ -5,7 +5,7 @@
|
|||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
@ -36,7 +36,7 @@ data class MessageVerificationRequestContent(
|
|||||||
@Json(name = "m.new_content") override val newContent: Content? = null,
|
@Json(name = "m.new_content") override val newContent: Content? = null,
|
||||||
// Not parsed, but set after, using the eventId
|
// Not parsed, but set after, using the eventId
|
||||||
override val transactionId: String? = null
|
override val transactionId: String? = null
|
||||||
) : MessageContent, VerificationInfoRequest {
|
) : MessageContent, VerificationInfoRequest {
|
||||||
|
|
||||||
override fun toEventContent() = toContent()
|
override fun toEventContent() = toContent()
|
||||||
}
|
}
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.sync.handler
|
package org.matrix.android.sdk.internal.session.sync.handler
|
||||||
|
|
||||||
|
import dagger.Lazy
|
||||||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM
|
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM
|
||||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
@ -26,8 +27,6 @@ import org.matrix.android.sdk.api.session.events.model.EventType
|
|||||||
import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent
|
import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
|
|
||||||
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
|
|
||||||
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
|
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.toDeviceTracingId
|
import org.matrix.android.sdk.internal.crypto.tasks.toDeviceTracingId
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
|
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
|
||||||
@ -38,15 +37,14 @@ import javax.inject.Inject
|
|||||||
private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
|
private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
|
||||||
|
|
||||||
internal class CryptoSyncHandler @Inject constructor(
|
internal class CryptoSyncHandler @Inject constructor(
|
||||||
private val cryptoService: DefaultCryptoService,
|
private val cryptoService: Lazy<DefaultCryptoService>,
|
||||||
private val verificationService: DefaultVerificationService
|
private val verificationService: DefaultVerificationService
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun handleToDevice(toDevice: ToDeviceSyncResponse, progressReporter: ProgressReporter? = null) {
|
suspend fun handleToDevice(eventList: List<Event>, progressReporter: ProgressReporter? = null) {
|
||||||
val total = toDevice.events?.size ?: 0
|
val total = eventList.size
|
||||||
toDevice.events
|
eventList.filter { isSupportedToDevice(it) }
|
||||||
?.filter { isSupportedToDevice(it) }
|
.forEachIndexed { index, event ->
|
||||||
?.forEachIndexed { index, event ->
|
|
||||||
progressReporter?.reportProgress(index * 100F / total)
|
progressReporter?.reportProgress(index * 100F / total)
|
||||||
// Decrypt event if necessary
|
// Decrypt event if necessary
|
||||||
Timber.tag(loggerTag.value).d("To device event msgid:${event.toDeviceTracingId()}")
|
Timber.tag(loggerTag.value).d("To device event msgid:${event.toDeviceTracingId()}")
|
||||||
@ -58,7 +56,7 @@ internal class CryptoSyncHandler @Inject constructor(
|
|||||||
} else {
|
} else {
|
||||||
Timber.tag(loggerTag.value).d("received to-device ${event.getClearType()} from:${event.senderId} msgid:${event.toDeviceTracingId()}")
|
Timber.tag(loggerTag.value).d("received to-device ${event.getClearType()} from:${event.senderId} msgid:${event.toDeviceTracingId()}")
|
||||||
verificationService.onToDeviceEvent(event)
|
verificationService.onToDeviceEvent(event)
|
||||||
cryptoService.onToDeviceEvent(event)
|
cryptoService.get().onToDeviceEvent(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -85,10 +83,6 @@ internal class CryptoSyncHandler @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSyncCompleted(syncResponse: SyncResponse) {
|
|
||||||
cryptoService.onSyncCompleted(syncResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt an encrypted event.
|
* Decrypt an encrypted event.
|
||||||
*
|
*
|
||||||
@ -101,12 +95,12 @@ internal class CryptoSyncHandler @Inject constructor(
|
|||||||
if (event.getClearType() == EventType.ENCRYPTED) {
|
if (event.getClearType() == EventType.ENCRYPTED) {
|
||||||
var result: MXEventDecryptionResult? = null
|
var result: MXEventDecryptionResult? = null
|
||||||
try {
|
try {
|
||||||
result = cryptoService.decryptEvent(event, timelineId ?: "")
|
result = cryptoService.get().decryptEvent(event, timelineId ?: "")
|
||||||
} catch (exception: MXCryptoError) {
|
} catch (exception: MXCryptoError) {
|
||||||
event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType // setCryptoError(exception.cryptoError)
|
event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType // setCryptoError(exception.cryptoError)
|
||||||
val senderKey = event.content.toModel<OlmEventContent>()?.senderKey ?: "<unknown sender key>"
|
val senderKey = event.content.toModel<OlmEventContent>()?.senderKey ?: "<unknown sender key>"
|
||||||
// try to find device id to ease log reading
|
// try to find device id to ease log reading
|
||||||
val deviceId = cryptoService.getCryptoDeviceInfo(event.senderId!!).firstOrNull {
|
val deviceId = cryptoService.get().getCryptoDeviceInfo(event.senderId!!).firstOrNull {
|
||||||
it.identityKey() == senderKey
|
it.identityKey() == senderKey
|
||||||
}?.deviceId ?: senderKey
|
}?.deviceId ?: senderKey
|
||||||
Timber.e("## CRYPTO | Failed to decrypt to device event from ${event.senderId}|$deviceId reason:<${event.mCryptoError ?: exception}>")
|
Timber.e("## CRYPTO | Failed to decrypt to device event from ${event.senderId}|$deviceId reason:<${event.mCryptoError ?: exception}>")
|
@ -0,0 +1,262 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2019 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
|
||||||
|
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
|
||||||
|
import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultCreateKeysBackupVersionTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultDeleteBackupTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionsDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultDeleteSessionsDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupLastVersionTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupVersionTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionsDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultGetSessionsDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionsDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultStoreSessionsDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultUpdateKeysBackupVersionTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteBackupTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionsDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteSessionsDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetSessionsDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultGetDeviceInfoTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultGetDevicesTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultInitializeCrossSigningTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendEventTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendToDeviceTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSetDeviceNameTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadKeysTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSignaturesTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSigningKeysTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.EncryptEventTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.InitializeCrossSigningTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.SendEventTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
|
||||||
|
import org.matrix.android.sdk.internal.database.RealmKeysUtils
|
||||||
|
import org.matrix.android.sdk.internal.di.CryptoDatabase
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
||||||
|
import org.matrix.android.sdk.internal.di.UserMd5
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
|
import org.matrix.android.sdk.internal.session.cache.ClearCacheTask
|
||||||
|
import org.matrix.android.sdk.internal.session.cache.RealmClearCacheTask
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
@Module
|
||||||
|
internal abstract class CryptoModule {
|
||||||
|
|
||||||
|
@Module
|
||||||
|
companion object {
|
||||||
|
internal fun getKeyAlias(userMd5: String) = "crypto_module_$userMd5"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
@CryptoDatabase
|
||||||
|
@SessionScope
|
||||||
|
fun providesRealmConfiguration(
|
||||||
|
@SessionFilesDirectory directory: File,
|
||||||
|
@UserMd5 userMd5: String,
|
||||||
|
realmKeysUtils: RealmKeysUtils,
|
||||||
|
realmCryptoStoreMigration: RealmCryptoStoreMigration
|
||||||
|
): RealmConfiguration {
|
||||||
|
return RealmConfiguration.Builder()
|
||||||
|
.directory(directory)
|
||||||
|
.apply {
|
||||||
|
realmKeysUtils.configureEncryption(this, getKeyAlias(userMd5))
|
||||||
|
}
|
||||||
|
.name("crypto_store.realm")
|
||||||
|
.modules(RealmCryptoStoreModule())
|
||||||
|
.allowWritesOnUiThread(true)
|
||||||
|
.schemaVersion(realmCryptoStoreMigration.schemaVersion)
|
||||||
|
.migration(realmCryptoStoreMigration)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
@SessionScope
|
||||||
|
fun providesCryptoCoroutineScope(coroutineDispatchers: MatrixCoroutineDispatchers): CoroutineScope {
|
||||||
|
return CoroutineScope(SupervisorJob() + coroutineDispatchers.crypto)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
@CryptoDatabase
|
||||||
|
fun providesClearCacheTask(@CryptoDatabase realmConfiguration: RealmConfiguration): ClearCacheTask {
|
||||||
|
return RealmClearCacheTask(realmConfiguration)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
@SessionScope
|
||||||
|
fun providesCryptoAPI(retrofit: Retrofit): CryptoApi {
|
||||||
|
return retrofit.create(CryptoApi::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Provides
|
||||||
|
@SessionScope
|
||||||
|
fun providesRoomKeysAPI(retrofit: Retrofit): RoomKeysApi {
|
||||||
|
return retrofit.create(RoomKeysApi::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindCryptoService(service: DefaultCryptoService): CryptoService
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindKeysBackupService(service: DefaultKeysBackupService): KeysBackupService
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindDeleteDeviceTask(task: DefaultDeleteDeviceTask): DeleteDeviceTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetDevicesTask(task: DefaultGetDevicesTask): GetDevicesTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetDeviceInfoTask(task: DefaultGetDeviceInfoTask): GetDeviceInfoTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSetDeviceNameTask(task: DefaultSetDeviceNameTask): SetDeviceNameTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindUploadKeysTask(task: DefaultUploadKeysTask): UploadKeysTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindUploadSigningKeysTask(task: DefaultUploadSigningKeysTask): UploadSigningKeysTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindUploadSignaturesTask(task: DefaultUploadSignaturesTask): UploadSignaturesTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindDownloadKeysForUsersTask(task: DefaultDownloadKeysForUsers): DownloadKeysForUsersTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindCreateKeysBackupVersionTask(task: DefaultCreateKeysBackupVersionTask): CreateKeysBackupVersionTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindDeleteBackupTask(task: DefaultDeleteBackupTask): DeleteBackupTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindDeleteRoomSessionDataTask(task: DefaultDeleteRoomSessionDataTask): DeleteRoomSessionDataTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindDeleteRoomSessionsDataTask(task: DefaultDeleteRoomSessionsDataTask): DeleteRoomSessionsDataTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindDeleteSessionsDataTask(task: DefaultDeleteSessionsDataTask): DeleteSessionsDataTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetKeysBackupLastVersionTask(task: DefaultGetKeysBackupLastVersionTask): GetKeysBackupLastVersionTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetKeysBackupVersionTask(task: DefaultGetKeysBackupVersionTask): GetKeysBackupVersionTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetRoomSessionDataTask(task: DefaultGetRoomSessionDataTask): GetRoomSessionDataTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetRoomSessionsDataTask(task: DefaultGetRoomSessionsDataTask): GetRoomSessionsDataTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetSessionsDataTask(task: DefaultGetSessionsDataTask): GetSessionsDataTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindStoreRoomSessionDataTask(task: DefaultStoreRoomSessionDataTask): StoreRoomSessionDataTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindStoreRoomSessionsDataTask(task: DefaultStoreRoomSessionsDataTask): StoreRoomSessionsDataTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindStoreSessionsDataTask(task: DefaultStoreSessionsDataTask): StoreSessionsDataTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindUpdateKeysBackupVersionTask(task: DefaultUpdateKeysBackupVersionTask): UpdateKeysBackupVersionTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSendToDeviceTask(task: DefaultSendToDeviceTask): SendToDeviceTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindEncryptEventTask(task: DefaultEncryptEventTask): EncryptEventTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSendVerificationMessageTask(task: DefaultSendVerificationMessageTask): SendVerificationMessageTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(task: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindVerificationService(service: DefaultVerificationService): VerificationService
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindInitalizeCrossSigningTask(task: DefaultInitializeCrossSigningTask): InitializeCrossSigningTask
|
||||||
|
}
|
@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class DecryptRoomEventUseCase @Inject constructor(
|
||||||
|
private val olmDevice: MXOlmDevice,
|
||||||
|
private val cryptoStore: IMXCryptoStore,
|
||||||
|
private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(event: Event, requestKeysOnFail: Boolean = true): MXEventDecryptionResult {
|
||||||
|
if (event.roomId.isNullOrBlank()) {
|
||||||
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||||
|
}
|
||||||
|
|
||||||
|
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
|
||||||
|
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||||
|
|
||||||
|
if (encryptedEventContent.senderKey.isNullOrBlank() ||
|
||||||
|
encryptedEventContent.sessionId.isNullOrBlank() ||
|
||||||
|
encryptedEventContent.ciphertext.isNullOrBlank()) {
|
||||||
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val olmDecryptionResult = olmDevice.decryptGroupMessage(
|
||||||
|
encryptedEventContent.ciphertext,
|
||||||
|
event.roomId,
|
||||||
|
"",
|
||||||
|
eventId = event.eventId.orEmpty(),
|
||||||
|
encryptedEventContent.sessionId,
|
||||||
|
encryptedEventContent.senderKey
|
||||||
|
)
|
||||||
|
if (olmDecryptionResult.payload != null) {
|
||||||
|
return MXEventDecryptionResult(
|
||||||
|
clearEvent = olmDecryptionResult.payload,
|
||||||
|
senderCurve25519Key = olmDecryptionResult.senderKey,
|
||||||
|
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
|
||||||
|
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain
|
||||||
|
.orEmpty(),
|
||||||
|
isSafe = olmDecryptionResult.isSafe.orFalse()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
|
||||||
|
}
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
if (throwable is MXCryptoError.OlmError) {
|
||||||
|
// TODO Check the value of .message
|
||||||
|
if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
|
||||||
|
// So we know that session, but it's ratcheted and we can't decrypt at that index
|
||||||
|
// Check if partially withheld
|
||||||
|
val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)
|
||||||
|
if (withHeldInfo != null) {
|
||||||
|
// Encapsulate as withHeld exception
|
||||||
|
throw MXCryptoError.Base(
|
||||||
|
MXCryptoError.ErrorType.KEYS_WITHHELD,
|
||||||
|
withHeldInfo.code?.value ?: "",
|
||||||
|
withHeldInfo.reason
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw MXCryptoError.Base(
|
||||||
|
MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX,
|
||||||
|
"UNKNOWN_MESSAGE_INDEX",
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
|
||||||
|
val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason)
|
||||||
|
|
||||||
|
throw MXCryptoError.Base(
|
||||||
|
MXCryptoError.ErrorType.OLM,
|
||||||
|
reason,
|
||||||
|
detailedReason
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (throwable is MXCryptoError.Base) {
|
||||||
|
if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
||||||
|
// Check if it was withheld by sender to enrich error code
|
||||||
|
val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)
|
||||||
|
if (withHeldInfo != null) {
|
||||||
|
if (requestKeysOnFail) {
|
||||||
|
requestKeysForEvent(event)
|
||||||
|
}
|
||||||
|
// Encapsulate as withHeld exception
|
||||||
|
throw MXCryptoError.Base(
|
||||||
|
MXCryptoError.ErrorType.KEYS_WITHHELD,
|
||||||
|
withHeldInfo.code?.value ?: "",
|
||||||
|
withHeldInfo.reason
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestKeysOnFail) {
|
||||||
|
requestKeysForEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw throwable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestKeysForEvent(event: Event) {
|
||||||
|
outgoingKeyRequestManager.requestKeyForEvent(event, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun decryptAndSaveResult(event: Event) {
|
||||||
|
tryOrNull(message = "Unable to decrypt the event") {
|
||||||
|
invoke(event)
|
||||||
|
}
|
||||||
|
?.let { result ->
|
||||||
|
event.mxDecryptionResult = OlmDecryptionResult(
|
||||||
|
payload = result.clearEvent,
|
||||||
|
senderKey = result.senderCurve25519Key,
|
||||||
|
keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) },
|
||||||
|
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain,
|
||||||
|
isSafe = result.isSafe
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,7 +53,6 @@ import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListen
|
|||||||
import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
|
import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
|
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo
|
||||||
@ -73,7 +72,10 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
|||||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
|
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.shouldShareHistory
|
import org.matrix.android.sdk.api.session.room.model.shouldShareHistory
|
||||||
|
import org.matrix.android.sdk.api.session.sync.model.DeviceListResponse
|
||||||
|
import org.matrix.android.sdk.api.session.sync.model.DeviceOneTimeKeysCountSyncResponse
|
||||||
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
|
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
|
||||||
|
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
|
import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
|
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
|
||||||
@ -86,6 +88,7 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningSe
|
|||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE
|
import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE
|
||||||
import org.matrix.android.sdk.internal.crypto.model.SessionInfo
|
import org.matrix.android.sdk.internal.crypto.model.SessionInfo
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadBody
|
||||||
import org.matrix.android.sdk.internal.crypto.model.toRest
|
import org.matrix.android.sdk.internal.crypto.model.toRest
|
||||||
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
@ -103,9 +106,7 @@ import org.matrix.android.sdk.internal.extensions.foldToCallback
|
|||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
import org.matrix.android.sdk.internal.session.StreamEventsManager
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.session.sync.handler.CryptoSyncHandler
|
||||||
import org.matrix.android.sdk.internal.task.TaskThread
|
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
|
||||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
import org.matrix.android.sdk.internal.task.launchToCallback
|
||||||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
||||||
import org.matrix.android.sdk.internal.util.time.Clock
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
@ -181,18 +182,18 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||||
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val taskExecutor: TaskExecutor,
|
|
||||||
private val cryptoCoroutineScope: CoroutineScope,
|
private val cryptoCoroutineScope: CoroutineScope,
|
||||||
private val eventDecryptor: EventDecryptor,
|
private val eventDecryptor: EventDecryptor,
|
||||||
private val verificationMessageProcessor: VerificationMessageProcessor,
|
private val verificationMessageProcessor: VerificationMessageProcessor,
|
||||||
private val liveEventManager: Lazy<StreamEventsManager>,
|
private val liveEventManager: Lazy<StreamEventsManager>,
|
||||||
private val unrequestedForwardManager: UnRequestedForwardManager,
|
private val unrequestedForwardManager: UnRequestedForwardManager,
|
||||||
|
private val cryptoSyncHandler: CryptoSyncHandler,
|
||||||
) : CryptoService {
|
) : CryptoService {
|
||||||
|
|
||||||
private val isStarting = AtomicBoolean(false)
|
private val isStarting = AtomicBoolean(false)
|
||||||
private val isStarted = AtomicBoolean(false)
|
private val isStarted = AtomicBoolean(false)
|
||||||
|
|
||||||
fun onStateEvent(roomId: String, event: Event) {
|
override suspend fun onStateEvent(roomId: String, event: Event) {
|
||||||
when (event.type) {
|
when (event.type) {
|
||||||
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
|
||||||
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
|
||||||
@ -200,7 +201,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean) {
|
override suspend fun onLiveEvent(roomId: String, event: Event, initialSync: Boolean) {
|
||||||
// handle state events
|
// handle state events
|
||||||
if (event.isStateEvent()) {
|
if (event.isStateEvent()) {
|
||||||
when (event.type) {
|
when (event.type) {
|
||||||
@ -211,10 +212,10 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handle verification
|
// handle verification
|
||||||
if (!isInitialSync) {
|
if (!initialSync) {
|
||||||
if (event.type != null && verificationMessageProcessor.shouldProcess(event.type)) {
|
if (event.type != null && verificationMessageProcessor.shouldProcess(event.type)) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.dmVerif) {
|
withContext(coroutineDispatchers.dmVerif) {
|
||||||
verificationMessageProcessor.process(event)
|
verificationMessageProcessor.process(roomId, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -222,69 +223,48 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
|
|
||||||
// val gossipingBuffer = mutableListOf<Event>()
|
// val gossipingBuffer = mutableListOf<Event>()
|
||||||
|
|
||||||
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
|
override suspend fun setDeviceName(deviceId: String, deviceName: String) {
|
||||||
setDeviceNameTask
|
setDeviceNameTask
|
||||||
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
|
.execute(SetDeviceNameTask.Params(deviceId, deviceName))
|
||||||
this.executionThread = TaskThread.CRYPTO
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
this.callback = object : MatrixCallback<Unit> {
|
downloadKeys(listOf(userId), true)
|
||||||
override fun onSuccess(data: Unit) {
|
}
|
||||||
// bg refresh of crypto device
|
|
||||||
downloadKeys(listOf(userId), true, NoOpMatrixCallback())
|
|
||||||
callback.onSuccess(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
callback.onFailure(failure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>) {
|
override suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
|
||||||
deleteDevices(listOf(deviceId), userInteractiveAuthInterceptor, callback)
|
deleteDevices(listOf(deviceId), userInteractiveAuthInterceptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun deleteDevices(deviceIds: List<String>, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>) {
|
override suspend fun deleteDevices(deviceIds: List<String>, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
|
||||||
deleteDeviceTask
|
withContext(coroutineDispatchers.crypto) {
|
||||||
.configureWith(DeleteDeviceTask.Params(deviceIds, userInteractiveAuthInterceptor, null)) {
|
deleteDeviceTask
|
||||||
this.executionThread = TaskThread.CRYPTO
|
.execute(DeleteDeviceTask.Params(deviceIds, userInteractiveAuthInterceptor, null))
|
||||||
this.callback = callback
|
}
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
|
override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
|
||||||
return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version
|
return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMyDevice(): CryptoDeviceInfo {
|
override fun getMyCryptoDevice(): CryptoDeviceInfo {
|
||||||
return myDeviceInfoHolder.get().myDevice
|
return myDeviceInfoHolder.get().myDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>) {
|
override suspend fun fetchDevicesList(): List<DeviceInfo> {
|
||||||
getDevicesTask
|
val data = getDevicesTask
|
||||||
.configureWith {
|
.execute(Unit)
|
||||||
// this.executionThread = TaskThread.CRYPTO
|
cryptoStore.saveMyDevicesInfo(data.devices.orEmpty())
|
||||||
this.callback = object : MatrixCallback<DevicesListResponse> {
|
return data.devices.orEmpty()
|
||||||
override fun onFailure(failure: Throwable) {
|
|
||||||
callback.onFailure(failure)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSuccess(data: DevicesListResponse) {
|
|
||||||
// Save in local DB
|
|
||||||
cryptoStore.saveMyDevicesInfo(data.devices.orEmpty())
|
|
||||||
callback.onSuccess(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMyDevicesInfoLive(): LiveData<List<DeviceInfo>> {
|
override fun getMyDevicesInfoLive(): LiveData<List<DeviceInfo>> {
|
||||||
return cryptoStore.getLiveMyDevicesInfo()
|
return cryptoStore.getLiveMyDevicesInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun fetchDeviceInfo(deviceId: String): DeviceInfo {
|
||||||
|
return getDeviceInfoTask.execute(GetDeviceInfoTask.Params(deviceId))
|
||||||
|
}
|
||||||
|
|
||||||
override fun getMyDevicesInfoLive(deviceId: String): LiveData<Optional<DeviceInfo>> {
|
override fun getMyDevicesInfoLive(deviceId: String): LiveData<Optional<DeviceInfo>> {
|
||||||
return cryptoStore.getLiveMyDevicesInfo(deviceId)
|
return cryptoStore.getLiveMyDevicesInfo(deviceId)
|
||||||
}
|
}
|
||||||
@ -293,8 +273,10 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
return cryptoStore.getMyDevicesInfo()
|
return cryptoStore.getMyDevicesInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
override suspend fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
||||||
return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
return withContext(coroutineDispatchers.io) {
|
||||||
|
cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -312,7 +294,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
*
|
*
|
||||||
* @return true if the crypto is started
|
* @return true if the crypto is started
|
||||||
*/
|
*/
|
||||||
fun isStarted(): Boolean {
|
override fun isStarted(): Boolean {
|
||||||
return isStarted.get()
|
return isStarted.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,14 +314,12 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* devices.
|
* devices.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
fun start() {
|
override fun start() {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
internalStart()
|
internalStart()
|
||||||
}
|
tryOrNull("Failed to update device list on start") {
|
||||||
// Just update
|
fetchDevicesList()
|
||||||
fetchDevicesList(NoOpMatrixCallback())
|
}
|
||||||
|
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
|
||||||
cryptoStore.tidyUpDataBase()
|
cryptoStore.tidyUpDataBase()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -367,8 +347,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSyncWillProcess(isInitialSync: Boolean) {
|
override suspend fun onSyncWillProcess(isInitialSync: Boolean) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
if (isInitialSync) {
|
if (isInitialSync) {
|
||||||
try {
|
try {
|
||||||
// On initial sync, we start all our tracking from
|
// On initial sync, we start all our tracking from
|
||||||
@ -391,6 +371,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
isStarting.set(true)
|
isStarting.set(true)
|
||||||
|
ensureDevice()
|
||||||
|
|
||||||
// Open the store
|
// Open the store
|
||||||
cryptoStore.open()
|
cryptoStore.open()
|
||||||
@ -402,7 +383,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
/**
|
/**
|
||||||
* Close the crypto.
|
* Close the crypto.
|
||||||
*/
|
*/
|
||||||
fun close() = runBlocking(coroutineDispatchers.crypto) {
|
override fun close() = runBlocking(coroutineDispatchers.crypto) {
|
||||||
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
|
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
|
||||||
incomingKeyRequestManager.close()
|
incomingKeyRequestManager.close()
|
||||||
outgoingKeyRequestManager.close()
|
outgoingKeyRequestManager.close()
|
||||||
@ -431,80 +412,84 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
*
|
*
|
||||||
* @param syncResponse the syncResponse
|
* @param syncResponse the syncResponse
|
||||||
*/
|
*/
|
||||||
fun onSyncCompleted(syncResponse: SyncResponse) {
|
override suspend fun onSyncCompleted(syncResponse: SyncResponse) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
// if (syncResponse.deviceLists != null) {
|
||||||
runCatching {
|
// deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
|
||||||
if (syncResponse.deviceLists != null) {
|
// }
|
||||||
deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
|
// if (syncResponse.deviceOneTimeKeysCount != null) {
|
||||||
}
|
// val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
|
||||||
if (syncResponse.deviceOneTimeKeysCount != null) {
|
// oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
|
||||||
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
|
// }
|
||||||
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
// unwedge if needed
|
// unwedge if needed
|
||||||
try {
|
try {
|
||||||
eventDecryptor.unwedgeDevicesIfNeeded()
|
eventDecryptor.unwedgeDevicesIfNeeded()
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
Timber.tag(loggerTag.value).w("unwedgeDevicesIfNeeded failed")
|
Timber.tag(loggerTag.value).w("unwedgeDevicesIfNeeded failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// There is a limit of to_device events returned per sync.
|
// There is a limit of to_device events returned per sync.
|
||||||
// If we are in a case of such limited to_device sync we can't try to generate/upload
|
// If we are in a case of such limited to_device sync we can't try to generate/upload
|
||||||
// new otk now, because there might be some pending olm pre-key to_device messages that would fail if we rotate
|
// new otk now, because there might be some pending olm pre-key to_device messages that would fail if we rotate
|
||||||
// the old otk too early. In this case we want to wait for the pending to_device before doing anything
|
// the old otk too early. In this case we want to wait for the pending to_device before doing anything
|
||||||
// As per spec:
|
// As per spec:
|
||||||
// If there is a large queue of send-to-device messages, the server should limit the number sent in each /sync response.
|
// If there is a large queue of send-to-device messages, the server should limit the number sent in each /sync response.
|
||||||
// 100 messages is recommended as a reasonable limit.
|
// 100 messages is recommended as a reasonable limit.
|
||||||
// The limit is not part of the spec, so it's probably safer to handle that when there are no more to_device ( so we are sure
|
// The limit is not part of the spec, so it's probably safer to handle that when there are no more to_device ( so we are sure
|
||||||
// that there are no pending to_device
|
// that there are no pending to_device
|
||||||
val toDevices = syncResponse.toDevice?.events.orEmpty()
|
val toDevices = syncResponse.toDevice?.events.orEmpty()
|
||||||
if (isStarted() && toDevices.isEmpty()) {
|
if (isStarted() && toDevices.isEmpty()) {
|
||||||
// Make sure we process to-device messages before generating new one-time-keys #2782
|
// Make sure we process to-device messages before generating new one-time-keys #2782
|
||||||
deviceListManager.refreshOutdatedDeviceLists()
|
deviceListManager.refreshOutdatedDeviceLists()
|
||||||
// The presence of device_unused_fallback_key_types indicates that the server supports fallback keys.
|
// The presence of device_unused_fallback_key_types indicates that the server supports fallback keys.
|
||||||
// If there's no unused signed_curve25519 fallback key we need a new one.
|
// If there's no unused signed_curve25519 fallback key we need a new one.
|
||||||
if (syncResponse.deviceUnusedFallbackKeyTypes != null &&
|
if (syncResponse.deviceUnusedFallbackKeyTypes != null &&
|
||||||
// Generate a fallback key only if the server does not already have an unused fallback key.
|
// Generate a fallback key only if the server does not already have an unused fallback key.
|
||||||
!syncResponse.deviceUnusedFallbackKeyTypes.contains(KEY_SIGNED_CURVE_25519_TYPE)) {
|
!syncResponse.deviceUnusedFallbackKeyTypes.contains(KEY_SIGNED_CURVE_25519_TYPE)) {
|
||||||
oneTimeKeysUploader.needsNewFallback()
|
oneTimeKeysUploader.needsNewFallback()
|
||||||
}
|
}
|
||||||
|
|
||||||
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process pending key requests
|
// Process pending key requests
|
||||||
try {
|
try {
|
||||||
if (toDevices.isEmpty()) {
|
if (toDevices.isEmpty()) {
|
||||||
// this is not blocking
|
// this is not blocking
|
||||||
outgoingKeyRequestManager.requireProcessAllPendingKeyRequests()
|
outgoingKeyRequestManager.requireProcessAllPendingKeyRequests()
|
||||||
} else {
|
} else {
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
.w("Don't process key requests yet as there might be more to_device to catchup")
|
.w("Don't process key requests yet as there might be more to_device to catchup")
|
||||||
}
|
}
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
// just for safety but should not throw
|
// just for safety but should not throw
|
||||||
Timber.tag(loggerTag.value).w("failed to process pending request")
|
Timber.tag(loggerTag.value).w("failed to process pending request")
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
incomingKeyRequestManager.processIncomingRequests()
|
incomingKeyRequestManager.processIncomingRequests()
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
// just for safety but should not throw
|
// just for safety but should not throw
|
||||||
Timber.tag(loggerTag.value).w("failed to process incoming room key requests")
|
Timber.tag(loggerTag.value).w("failed to process incoming room key requests")
|
||||||
}
|
}
|
||||||
|
|
||||||
unrequestedForwardManager.postSyncProcessParkedKeysIfNeeded(clock.epochMillis()) { events ->
|
unrequestedForwardManager.postSyncProcessParkedKeysIfNeeded(clock.epochMillis()) { events ->
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
events.forEach {
|
events.forEach {
|
||||||
onRoomKeyEvent(it, true)
|
onRoomKeyEvent(it, true)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun logDbUsageInfo() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun setRoomUnBlacklistUnverifiedDevices(roomId: String) {
|
||||||
|
cryptoStore.blockUnverifiedDevicesInRoom(roomId, false)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a device by curve25519 identity key.
|
* Find a device by curve25519 identity key.
|
||||||
*
|
*
|
||||||
@ -512,11 +497,18 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param algorithm the encryption algorithm.
|
* @param algorithm the encryption algorithm.
|
||||||
* @return the device info, or null if not found / unsupported algorithm / crypto released
|
* @return the device info, or null if not found / unsupported algorithm / crypto released
|
||||||
*/
|
*/
|
||||||
override fun deviceWithIdentityKey(senderKey: String, algorithm: String): CryptoDeviceInfo? {
|
override suspend fun deviceWithIdentityKey(userId: String, senderKey: String, algorithm: String): CryptoDeviceInfo? {
|
||||||
return if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM && algorithm != MXCRYPTO_ALGORITHM_OLM) {
|
return if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM && algorithm != MXCRYPTO_ALGORITHM_OLM) {
|
||||||
// We only deal in olm keys
|
// We only deal in olm keys
|
||||||
null
|
null
|
||||||
} else cryptoStore.deviceWithIdentityKey(senderKey)
|
} else {
|
||||||
|
withContext(coroutineDispatchers.io) {
|
||||||
|
cryptoStore.deviceWithIdentityKey(senderKey).takeIf {
|
||||||
|
// check that the claimed user id matches
|
||||||
|
it?.userId == userId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -525,26 +517,32 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param userId the user id
|
* @param userId the user id
|
||||||
* @param deviceId the device id
|
* @param deviceId the device id
|
||||||
*/
|
*/
|
||||||
override fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
|
override suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
|
||||||
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
|
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
|
||||||
cryptoStore.getUserDevice(userId, deviceId)
|
withContext(coroutineDispatchers.io) {
|
||||||
|
cryptoStore.getUserDevice(userId, deviceId)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
|
// override fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
|
||||||
getDeviceInfoTask
|
// getDeviceInfoTask
|
||||||
.configureWith(GetDeviceInfoTask.Params(deviceId)) {
|
// .configureWith(GetDeviceInfoTask.Params(deviceId)) {
|
||||||
this.executionThread = TaskThread.CRYPTO
|
// this.executionThread = TaskThread.CRYPTO
|
||||||
this.callback = callback
|
// this.callback = callback
|
||||||
}
|
// }
|
||||||
.executeBy(taskExecutor)
|
// .executeBy(taskExecutor)
|
||||||
}
|
// }
|
||||||
|
|
||||||
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||||
return cryptoStore.getUserDeviceList(userId).orEmpty()
|
return cryptoStore.getUserDeviceList(userId).orEmpty()
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
// override fun getCryptoDeviceInfoFlow(userId: String): Flow<List<CryptoDeviceInfo>> {
|
||||||
|
// return cryptoStore.getUserDeviceListFlow(userId)
|
||||||
|
// }
|
||||||
|
|
||||||
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
|
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
|
||||||
return cryptoStore.getLiveDeviceList()
|
return cryptoStore.getLiveDeviceList()
|
||||||
@ -568,7 +566,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param devices the devices. Note that the verified member of the devices in this list will not be updated by this method.
|
* @param devices the devices. Note that the verified member of the devices in this list will not be updated by this method.
|
||||||
* @param callback the asynchronous callback
|
* @param callback the asynchronous callback
|
||||||
*/
|
*/
|
||||||
override fun setDevicesKnown(devices: List<MXDeviceInfo>, callback: MatrixCallback<Unit>?) {
|
fun setDevicesKnown(devices: List<MXDeviceInfo>, callback: MatrixCallback<Unit>?) {
|
||||||
// build a devices map
|
// build a devices map
|
||||||
val devicesIdListByUserId = devices.groupBy({ it.userId }, { it.deviceId })
|
val devicesIdListByUserId = devices.groupBy({ it.userId }, { it.deviceId })
|
||||||
|
|
||||||
@ -606,7 +604,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param userId the owner of the device
|
* @param userId the owner of the device
|
||||||
* @param deviceId the unique identifier for the device.
|
* @param deviceId the unique identifier for the device.
|
||||||
*/
|
*/
|
||||||
override fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
|
override suspend fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
|
||||||
setDeviceVerificationAction.handle(trustLevel, userId, deviceId)
|
setDeviceVerificationAction.handle(trustLevel, userId, deviceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -688,8 +686,10 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
/**
|
/**
|
||||||
* @return the stored device keys for a user.
|
* @return the stored device keys for a user.
|
||||||
*/
|
*/
|
||||||
override fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
|
override suspend fun getUserDevices(userId: String): List<CryptoDeviceInfo> {
|
||||||
return cryptoStore.getUserDevices(userId)?.values?.toMutableList() ?: ArrayList()
|
return withContext(coroutineDispatchers.io) {
|
||||||
|
cryptoStore.getUserDevices(userId)?.values?.toList().orEmpty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isEncryptionEnabledForInvitedUser(): Boolean {
|
private fun isEncryptionEnabledForInvitedUser(): Boolean {
|
||||||
@ -720,14 +720,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param roomId the room identifier the event will be sent.
|
* @param roomId the room identifier the event will be sent.
|
||||||
* @param callback the asynchronous callback
|
* @param callback the asynchronous callback
|
||||||
*/
|
*/
|
||||||
override fun encryptEventContent(
|
override suspend fun encryptEventContent(
|
||||||
eventContent: Content,
|
eventContent: Content,
|
||||||
eventType: String,
|
eventType: String,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
callback: MatrixCallback<MXEncryptEventContentResult>
|
): MXEncryptEventContentResult {
|
||||||
) {
|
|
||||||
// moved to crypto scope to have uptodate values
|
// moved to crypto scope to have uptodate values
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
return withContext(coroutineDispatchers.crypto) {
|
||||||
val userIds = getRoomUserIds(roomId)
|
val userIds = getRoomUserIds(roomId)
|
||||||
var alg = roomEncryptorsStore.get(roomId)
|
var alg = roomEncryptorsStore.get(roomId)
|
||||||
if (alg == null) {
|
if (alg == null) {
|
||||||
@ -742,11 +741,9 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
if (safeAlgorithm != null) {
|
if (safeAlgorithm != null) {
|
||||||
val t0 = clock.epochMillis()
|
val t0 = clock.epochMillis()
|
||||||
Timber.tag(loggerTag.value).v("encryptEventContent() starts")
|
Timber.tag(loggerTag.value).v("encryptEventContent() starts")
|
||||||
runCatching {
|
val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
|
||||||
val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
|
Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms")
|
||||||
Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms")
|
return@withContext MXEncryptEventContentResult(content, EventType.ENCRYPTED)
|
||||||
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
|
|
||||||
}.foldToCallback(callback)
|
|
||||||
} else {
|
} else {
|
||||||
val algorithm = getEncryptionAlgorithm(roomId)
|
val algorithm = getEncryptionAlgorithm(roomId)
|
||||||
val reason = String.format(
|
val reason = String.format(
|
||||||
@ -754,7 +751,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON
|
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON
|
||||||
)
|
)
|
||||||
Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason")
|
Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason")
|
||||||
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason)))
|
throw Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -782,17 +779,6 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
return internalDecryptEvent(event, timeline)
|
return internalDecryptEvent(event, timeline)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypt an event asynchronously.
|
|
||||||
*
|
|
||||||
* @param event the raw event.
|
|
||||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
|
||||||
* @param callback the callback to return data or null
|
|
||||||
*/
|
|
||||||
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
|
|
||||||
eventDecryptor.decryptEventAsync(event, timeline, callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt an event.
|
* Decrypt an event.
|
||||||
*
|
*
|
||||||
@ -802,7 +788,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
@Throws(MXCryptoError::class)
|
@Throws(MXCryptoError::class)
|
||||||
private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||||
return eventDecryptor.decryptEvent(event, timeline)
|
return withContext(coroutineDispatchers.crypto) { eventDecryptor.decryptEvent(event, timeline) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -862,7 +848,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param event the key event.
|
* @param event the key event.
|
||||||
* @param acceptUnrequested, if true it will force to accept unrequested keys.
|
* @param acceptUnrequested, if true it will force to accept unrequested keys.
|
||||||
*/
|
*/
|
||||||
private fun onRoomKeyEvent(event: Event, acceptUnrequested: Boolean = false) {
|
private suspend fun onRoomKeyEvent(event: Event, acceptUnrequested: Boolean = false) {
|
||||||
val roomKeyContent = event.getDecryptedContent().toModel<RoomKeyContent>() ?: return
|
val roomKeyContent = event.getDecryptedContent().toModel<RoomKeyContent>() ?: return
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
.i("onRoomKeyEvent(f:$acceptUnrequested) from: ${event.senderId} type<${event.getClearType()}> , session<${roomKeyContent.sessionId}>")
|
.i("onRoomKeyEvent(f:$acceptUnrequested) from: ${event.senderId} type<${event.getClearType()}> , session<${roomKeyContent.sessionId}>")
|
||||||
@ -918,19 +904,27 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
): Boolean {
|
): Boolean {
|
||||||
return when (secretName) {
|
return when (secretName) {
|
||||||
MASTER_KEY_SSSS_NAME -> {
|
MASTER_KEY_SSSS_NAME -> {
|
||||||
crossSigningService.onSecretMSKGossip(secretValue)
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
|
crossSigningService.onSecretMSKGossip(secretValue)
|
||||||
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
SELF_SIGNING_KEY_SSSS_NAME -> {
|
SELF_SIGNING_KEY_SSSS_NAME -> {
|
||||||
crossSigningService.onSecretSSKGossip(secretValue)
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
|
crossSigningService.onSecretSSKGossip(secretValue)
|
||||||
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
USER_SIGNING_KEY_SSSS_NAME -> {
|
USER_SIGNING_KEY_SSSS_NAME -> {
|
||||||
crossSigningService.onSecretUSKGossip(secretValue)
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
|
crossSigningService.onSecretUSKGossip(secretValue)
|
||||||
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
KEYBACKUP_SECRET_SSSS_NAME -> {
|
KEYBACKUP_SECRET_SSSS_NAME -> {
|
||||||
keysBackupService.onSecretKeyGossip(secretValue)
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
|
keysBackupService.onSecretKeyGossip(secretValue)
|
||||||
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
else -> false
|
else -> false
|
||||||
@ -943,13 +937,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param roomId the room Id
|
* @param roomId the room Id
|
||||||
* @param event the encryption event.
|
* @param event the encryption event.
|
||||||
*/
|
*/
|
||||||
private fun onRoomEncryptionEvent(roomId: String, event: Event) {
|
private suspend fun onRoomEncryptionEvent(roomId: String, event: Event) {
|
||||||
if (!event.isStateEvent()) {
|
if (!event.isStateEvent()) {
|
||||||
// Ignore
|
// Ignore
|
||||||
Timber.tag(loggerTag.value).w("Invalid encryption event")
|
Timber.tag(loggerTag.value).w("Invalid encryption event")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
withContext(coroutineDispatchers.io) {
|
||||||
val userIds = getRoomUserIds(roomId)
|
val userIds = getRoomUserIds(roomId)
|
||||||
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
|
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
|
||||||
}
|
}
|
||||||
@ -967,7 +961,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
* @param roomId the room Id
|
* @param roomId the room Id
|
||||||
* @param event the membership event causing the change
|
* @param event the membership event causing the change
|
||||||
*/
|
*/
|
||||||
private fun onRoomMembershipEvent(roomId: String, event: Event) {
|
private suspend fun onRoomMembershipEvent(roomId: String, event: Event) {
|
||||||
// because the encryption event can be after the join/invite in the same batch
|
// because the encryption event can be after the join/invite in the same batch
|
||||||
event.stateKey?.let { _ ->
|
event.stateKey?.let { _ ->
|
||||||
val roomMember: RoomMemberContent? = event.content.toModel()
|
val roomMember: RoomMemberContent? = event.content.toModel()
|
||||||
@ -976,37 +970,39 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
unrequestedForwardManager.onInviteReceived(roomId, event.senderId.orEmpty(), clock.epochMillis())
|
unrequestedForwardManager.onInviteReceived(roomId, event.senderId.orEmpty(), clock.epochMillis())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
roomEncryptorsStore.get(roomId) ?: /* No encrypting in this room */ return
|
roomEncryptorsStore.get(roomId) ?: /* No encrypting in this room */ return
|
||||||
|
withContext(coroutineDispatchers.io) {
|
||||||
event.stateKey?.let { userId ->
|
event.stateKey?.let { userId ->
|
||||||
val roomMember: RoomMemberContent? = event.content.toModel()
|
val roomMember: RoomMemberContent? = event.content.toModel()
|
||||||
val membership = roomMember?.membership
|
val membership = roomMember?.membership
|
||||||
if (membership == Membership.JOIN) {
|
if (membership == Membership.JOIN) {
|
||||||
// make sure we are tracking the deviceList for this user.
|
// make sure we are tracking the deviceList for this user.
|
||||||
deviceListManager.startTrackingDeviceList(listOf(userId))
|
deviceListManager.startTrackingDeviceList(listOf(userId))
|
||||||
} else if (membership == Membership.INVITE &&
|
} else if (membership == Membership.INVITE &&
|
||||||
shouldEncryptForInvitedMembers(roomId) &&
|
shouldEncryptForInvitedMembers(roomId) &&
|
||||||
isEncryptionEnabledForInvitedUser()) {
|
isEncryptionEnabledForInvitedUser()) {
|
||||||
// track the deviceList for this invited user.
|
// track the deviceList for this invited user.
|
||||||
// Caution: there's a big edge case here in that federated servers do not
|
// Caution: there's a big edge case here in that federated servers do not
|
||||||
// know what other servers are in the room at the time they've been invited.
|
// know what other servers are in the room at the time they've been invited.
|
||||||
// They therefore will not send device updates if a user logs in whilst
|
// They therefore will not send device updates if a user logs in whilst
|
||||||
// their state is invite.
|
// their state is invite.
|
||||||
deviceListManager.startTrackingDeviceList(listOf(userId))
|
deviceListManager.startTrackingDeviceList(listOf(userId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) {
|
private suspend fun onRoomHistoryVisibilityEvent(roomId: String, event: Event) {
|
||||||
if (!event.isStateEvent()) return
|
if (!event.isStateEvent()) return
|
||||||
val eventContent = event.content.toModel<RoomHistoryVisibilityContent>()
|
val eventContent = event.content.toModel<RoomHistoryVisibilityContent>()
|
||||||
val historyVisibility = eventContent?.historyVisibility
|
val historyVisibility = eventContent?.historyVisibility
|
||||||
if (historyVisibility == null) {
|
withContext(coroutineDispatchers.io) {
|
||||||
cryptoStore.setShouldShareHistory(roomId, false)
|
if (historyVisibility == null) {
|
||||||
} else {
|
cryptoStore.setShouldShareHistory(roomId, false)
|
||||||
cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED)
|
} else {
|
||||||
cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory())
|
cryptoStore.setShouldEncryptForInvitedMembers(roomId, historyVisibility != RoomHistoryVisibility.JOINED)
|
||||||
|
cryptoStore.setShouldShareHistory(roomId, historyVisibility.shouldShareHistory())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1020,19 +1016,39 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
}
|
}
|
||||||
// Prepare the device keys data to send
|
// Prepare the device keys data to send
|
||||||
// Sign it
|
// Sign it
|
||||||
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
|
val myCryptoDevice = getMyCryptoDevice()
|
||||||
var rest = getMyDevice().toRest()
|
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myCryptoDevice.signalableJSONDictionary())
|
||||||
|
var rest = myCryptoDevice.toRest()
|
||||||
|
|
||||||
rest = rest.copy(
|
rest = rest.copy(
|
||||||
signatures = objectSigner.signObject(canonicalJson)
|
signatures = objectSigner.signObject(canonicalJson)
|
||||||
)
|
)
|
||||||
|
|
||||||
val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null, null)
|
val keyUploadBody = KeysUploadBody(
|
||||||
|
deviceKeys = rest,
|
||||||
|
)
|
||||||
|
val uploadDeviceKeysParams = UploadKeysTask.Params(keyUploadBody)
|
||||||
uploadKeysTask.execute(uploadDeviceKeysParams)
|
uploadKeysTask.execute(uploadDeviceKeysParams)
|
||||||
|
|
||||||
cryptoStore.setDeviceKeysUploaded(true)
|
cryptoStore.setDeviceKeysUploaded(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun receiveSyncChanges(
|
||||||
|
toDevice: ToDeviceSyncResponse?,
|
||||||
|
deviceChanges: DeviceListResponse?,
|
||||||
|
keyCounts: DeviceOneTimeKeysCountSyncResponse?
|
||||||
|
) {
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
|
deviceListManager.handleDeviceListsChanges(deviceChanges?.changed.orEmpty(), deviceChanges?.left.orEmpty())
|
||||||
|
if (keyCounts != null) {
|
||||||
|
val currentCount = keyCounts.signedCurve25519 ?: 0
|
||||||
|
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
cryptoSyncHandler.handleToDevice(toDevice?.events.orEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export the crypto keys.
|
* Export the crypto keys.
|
||||||
*
|
*
|
||||||
@ -1135,6 +1151,22 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun downloadKeysIfNeeded(userIds: List<String>, forceDownload: Boolean): MXUsersDevicesMap<CryptoDeviceInfo> {
|
||||||
|
return deviceListManager.downloadKeys(userIds, forceDownload)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getCryptoDeviceInfoList(userId: String): List<CryptoDeviceInfo> {
|
||||||
|
return cryptoStore.getUserDeviceList(userId).orEmpty()
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// fun getLiveCryptoDeviceInfoList(userId: String): Flow<List<CryptoDeviceInfo>> {
|
||||||
|
// cryptoStore.getLiveDeviceList(userId).asFlow()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// fun getLiveCryptoDeviceInfoList(userIds: List<String>): Flow<List<CryptoDeviceInfo>> {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the global override for whether the client should ever send encrypted
|
* Set the global override for whether the client should ever send encrypted
|
||||||
* messages to unverified devices.
|
* messages to unverified devices.
|
||||||
@ -1218,11 +1250,11 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
*
|
*
|
||||||
* @param event the event to decrypt again.
|
* @param event the event to decrypt again.
|
||||||
*/
|
*/
|
||||||
override fun reRequestRoomKeyForEvent(event: Event) {
|
override suspend fun reRequestRoomKeyForEvent(event: Event) {
|
||||||
outgoingKeyRequestManager.requestKeyForEvent(event, true)
|
outgoingKeyRequestManager.requestKeyForEvent(event, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requestRoomKeyForEvent(event: Event) {
|
suspend fun requestRoomKeyForEvent(event: Event) {
|
||||||
outgoingKeyRequestManager.requestKeyForEvent(event, false)
|
outgoingKeyRequestManager.requestKeyForEvent(event, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1268,12 +1300,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
return unknownDevices
|
return unknownDevices
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>>) {
|
suspend fun downloadKeys(userIds: List<String>, forceDownload: Boolean): MXUsersDevicesMap<CryptoDeviceInfo> {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
return deviceListManager.downloadKeys(userIds, forceDownload)
|
||||||
runCatching {
|
|
||||||
deviceListManager.downloadKeys(userIds, forceDownload)
|
|
||||||
}.foldToCallback(callback)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addNewSessionListener(newSessionListener: NewSessionListener) {
|
override fun addNewSessionListener(newSessionListener: NewSessionListener) {
|
||||||
@ -1337,8 +1365,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
return cryptoStore.getWithHeldMegolmSession(roomId, sessionId)
|
return cryptoStore.getWithHeldMegolmSession(roomId, sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>) {
|
override suspend fun prepareToEncrypt(roomId: String) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date")
|
Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date")
|
||||||
// Ensure to load all room members
|
// Ensure to load all room members
|
||||||
try {
|
try {
|
||||||
@ -1358,19 +1386,10 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
if (alg == null) {
|
if (alg == null) {
|
||||||
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
|
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
|
||||||
Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason")
|
Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason")
|
||||||
callback.onFailure(IllegalArgumentException("Missing algorithm"))
|
throw IllegalArgumentException("Missing algorithm")
|
||||||
return@launch
|
|
||||||
}
|
}
|
||||||
|
|
||||||
runCatching {
|
|
||||||
(alg as? IMXGroupEncryption)?.preshareKey(userIds)
|
(alg as? IMXGroupEncryption)?.preshareKey(userIds)
|
||||||
}.fold(
|
|
||||||
{ callback.onSuccess(Unit) },
|
|
||||||
{
|
|
||||||
Timber.tag(loggerTag.value).e(it, "prepareToEncrypt() failed.")
|
|
||||||
callback.onFailure(it)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1398,6 +1417,14 @@ internal class DefaultCryptoService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onE2ERoomMemberLoadedFromServer(roomId: String) {
|
||||||
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
|
val userIds = getRoomUserIds(roomId)
|
||||||
|
// Because of LL we might want to update tracked users
|
||||||
|
deviceListManager.startTrackingDeviceList(userIds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* For test only
|
* For test only
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
@ -61,7 +61,7 @@ internal class MyDeviceInfoHolder @Inject constructor(
|
|||||||
// myDevice.trustLevel = DeviceTrustLevel(crossSigned, true)
|
// myDevice.trustLevel = DeviceTrustLevel(crossSigned, true)
|
||||||
|
|
||||||
myDevice = CryptoDeviceInfo(
|
myDevice = CryptoDeviceInfo(
|
||||||
credentials.deviceId!!,
|
credentials.deviceId,
|
||||||
credentials.userId,
|
credentials.userId,
|
||||||
keys = keys,
|
keys = keys,
|
||||||
algorithms = MXCryptoAlgorithms.supportedAlgorithms(),
|
algorithms = MXCryptoAlgorithms.supportedAlgorithms(),
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXKey
|
import org.matrix.android.sdk.internal.crypto.model.MXKey
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadBody
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
|
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
|
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
@ -138,7 +139,7 @@ internal class OneTimeKeysUploader @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun fetchOtkCount(): Int? {
|
private suspend fun fetchOtkCount(): Int? {
|
||||||
return tryOrNull("Unable to get OTK count") {
|
return tryOrNull("Unable to get OTK count") {
|
||||||
val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null, null))
|
val result = uploadKeysTask.execute(UploadKeysTask.Params(KeysUploadBody()))
|
||||||
result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
|
result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,9 +228,11 @@ internal class OneTimeKeysUploader @Inject constructor(
|
|||||||
// For now, we set the device id explicitly, as we may not be using the
|
// For now, we set the device id explicitly, as we may not be using the
|
||||||
// same one as used in login.
|
// same one as used in login.
|
||||||
val uploadParams = UploadKeysTask.Params(
|
val uploadParams = UploadKeysTask.Params(
|
||||||
deviceKeys = null,
|
KeysUploadBody(
|
||||||
oneTimeKeys = oneTimeJson,
|
deviceKeys = null,
|
||||||
fallbackKeys = fallbackJson.takeIf { fallbackJson.isNotEmpty() }
|
oneTimeKeys = oneTimeJson,
|
||||||
|
fallbackKeys = fallbackJson.takeIf { fallbackJson.isNotEmpty() }
|
||||||
|
)
|
||||||
)
|
)
|
||||||
return uploadKeysTask.executeRetry(uploadParams, 3)
|
return uploadKeysTask.executeRetry(uploadParams, 3)
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright (c) 2019 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -74,11 +74,11 @@ internal class RoomDecryptorProvider @Inject constructor(
|
|||||||
val alg = when (algorithm) {
|
val alg = when (algorithm) {
|
||||||
MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create().apply {
|
MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create().apply {
|
||||||
this.newSessionListener = object : NewSessionListener {
|
this.newSessionListener = object : NewSessionListener {
|
||||||
override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
|
override fun onNewSession(roomId: String?, sessionId: String) {
|
||||||
// PR reviewer: the parameter has been renamed so is now in conflict with the parameter of getOrCreateRoomDecryptor
|
// PR reviewer: the parameter has been renamed so is now in conflict with the parameter of getOrCreateRoomDecryptor
|
||||||
newSessionListeners.toList().forEach {
|
newSessionListeners.toList().forEach {
|
||||||
try {
|
try {
|
||||||
it.onNewSession(roomId, senderKey, sessionId)
|
it.onNewSession(roomId, sessionId)
|
||||||
} catch (ignore: Throwable) {
|
} catch (ignore: Throwable) {
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -28,7 +28,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S
|
|||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
|
import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest
|
import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest
|
||||||
@ -36,7 +35,6 @@ import org.matrix.android.sdk.api.session.events.model.Event
|
|||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent
|
import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.util.toBase64NoPadding
|
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
|
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
@ -153,10 +151,7 @@ internal class SecretShareManager @Inject constructor(
|
|||||||
MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master
|
MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master
|
||||||
SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned
|
SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned
|
||||||
USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user
|
USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user
|
||||||
KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey
|
KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey?.toBase64()
|
||||||
?.let {
|
|
||||||
extractCurveKeyFromRecoveryKey(it)?.toBase64NoPadding()
|
|
||||||
}
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
if (secretValue == null) {
|
if (secretValue == null) {
|
||||||
@ -248,7 +243,7 @@ internal class SecretShareManager @Inject constructor(
|
|||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
withContext(coroutineDispatchers.io) {
|
withContext(coroutineDispatchers.io) {
|
||||||
sendToDeviceTask.executeRetry(params, 3)
|
sendToDeviceTask.execute(params)
|
||||||
}
|
}
|
||||||
Timber.tag(loggerTag.value)
|
Timber.tag(loggerTag.value)
|
||||||
.d("Secret request sent for $secretName to ${cryptoDeviceInfo.shortDebugString()}")
|
.d("Secret request sent for $secretName to ${cryptoDeviceInfo.shortDebugString()}")
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright (c) 2019 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -91,10 +91,21 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Let's now claim one time keys
|
// Let's now claim one time keys
|
||||||
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
|
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim.map)
|
||||||
val oneTimeKeys = withContext(coroutineDispatchers.io) {
|
val oneTimeKeysForUsers = withContext(coroutineDispatchers.io) {
|
||||||
oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, ONE_TIME_KEYS_RETRY_COUNT)
|
oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, ONE_TIME_KEYS_RETRY_COUNT)
|
||||||
}
|
}
|
||||||
|
val oneTimeKeys = MXUsersDevicesMap<MXKey>()
|
||||||
|
for ((userId, mapByUserId) in oneTimeKeysForUsers.oneTimeKeys.orEmpty()) {
|
||||||
|
for ((deviceId, deviceKey) in mapByUserId) {
|
||||||
|
val mxKey = MXKey.from(deviceKey)
|
||||||
|
if (mxKey != null) {
|
||||||
|
oneTimeKeys.setObject(userId, deviceId, mxKey)
|
||||||
|
} else {
|
||||||
|
Timber.e("## claimOneTimeKeysForUsersDevices : fail to create a MXKey")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// let now start olm session using the new otks
|
// let now start olm session using the new otks
|
||||||
devicesToCreateSessionWith.forEach { deviceInfo ->
|
devicesToCreateSessionWith.forEach { deviceInfo ->
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright (c) 2019 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -57,6 +57,7 @@ internal class MegolmSessionDataImporter @Inject constructor(
|
|||||||
progressListener: ProgressListener?
|
progressListener: ProgressListener?
|
||||||
): ImportRoomKeysResult {
|
): ImportRoomKeysResult {
|
||||||
val t0 = clock.epochMillis()
|
val t0 = clock.epochMillis()
|
||||||
|
val importedSession = mutableMapOf<String, MutableMap<String, MutableList<String>>>()
|
||||||
|
|
||||||
val totalNumbersOfKeys = megolmSessionsData.size
|
val totalNumbersOfKeys = megolmSessionsData.size
|
||||||
var lastProgress = 0
|
var lastProgress = 0
|
||||||
@ -70,18 +71,23 @@ internal class MegolmSessionDataImporter @Inject constructor(
|
|||||||
|
|
||||||
if (null != decrypting) {
|
if (null != decrypting) {
|
||||||
try {
|
try {
|
||||||
val sessionId = megolmSessionData.sessionId
|
val sessionId = megolmSessionData.sessionId ?: return@forEachIndexed
|
||||||
|
val senderKey = megolmSessionData.senderKey ?: return@forEachIndexed
|
||||||
|
val roomId = megolmSessionData.roomId ?: return@forEachIndexed
|
||||||
Timber.tag(loggerTag.value).v("## importRoomKeys retrieve senderKey ${megolmSessionData.senderKey} sessionId $sessionId")
|
Timber.tag(loggerTag.value).v("## importRoomKeys retrieve senderKey ${megolmSessionData.senderKey} sessionId $sessionId")
|
||||||
|
|
||||||
|
importedSession.getOrPut(roomId) { mutableMapOf() }
|
||||||
|
.getOrPut(senderKey) { mutableListOf() }
|
||||||
|
.add(sessionId)
|
||||||
totalNumbersOfImportedKeys++
|
totalNumbersOfImportedKeys++
|
||||||
|
|
||||||
// cancel any outstanding room key requests for this session
|
// cancel any outstanding room key requests for this session
|
||||||
|
|
||||||
Timber.tag(loggerTag.value).d("Imported megolm session $sessionId from backup=$fromBackup in ${megolmSessionData.roomId}")
|
Timber.tag(loggerTag.value).d("Imported megolm session $sessionId from backup=$fromBackup in ${megolmSessionData.roomId}")
|
||||||
outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded(
|
outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded(
|
||||||
megolmSessionData.sessionId ?: "",
|
sessionId,
|
||||||
megolmSessionData.roomId ?: "",
|
roomId,
|
||||||
megolmSessionData.senderKey ?: "",
|
senderKey,
|
||||||
tryOrNull {
|
tryOrNull {
|
||||||
olmInboundGroupSessionWrappers
|
olmInboundGroupSessionWrappers
|
||||||
.firstOrNull { it.session.sessionIdentifier() == megolmSessionData.sessionId }
|
.firstOrNull { it.session.sessionIdentifier() == megolmSessionData.sessionId }
|
||||||
@ -93,7 +99,7 @@ internal class MegolmSessionDataImporter @Inject constructor(
|
|||||||
// Have another go at decrypting events sent with this session
|
// Have another go at decrypting events sent with this session
|
||||||
when (decrypting) {
|
when (decrypting) {
|
||||||
is MXMegolmDecryption -> {
|
is MXMegolmDecryption -> {
|
||||||
decrypting.onNewSession(megolmSessionData.roomId, megolmSessionData.senderKey!!, sessionId!!)
|
decrypting.onNewSession(megolmSessionData.roomId, senderKey, sessionId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -121,6 +127,6 @@ internal class MegolmSessionDataImporter @Inject constructor(
|
|||||||
|
|
||||||
Timber.tag(loggerTag.value).v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)")
|
Timber.tag(loggerTag.value).v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)")
|
||||||
|
|
||||||
return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys)
|
return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys, importedSession)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright (c) 2020 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -29,7 +29,7 @@ internal class SetDeviceVerificationAction @Inject constructor(
|
|||||||
private val defaultKeysBackupService: DefaultKeysBackupService
|
private val defaultKeysBackupService: DefaultKeysBackupService
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
|
suspend fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
|
||||||
val device = cryptoStore.getUserDevice(userId, deviceId)
|
val device = cryptoStore.getUserDevice(userId, deviceId)
|
||||||
|
|
||||||
// Sanity check
|
// Sanity check
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright (c) 2019 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -43,5 +43,5 @@ internal interface IMXDecrypting {
|
|||||||
* @param defaultKeysBackupService the keys backup service
|
* @param defaultKeysBackupService the keys backup service
|
||||||
* @param forceAccept the keys backup service
|
* @param forceAccept the keys backup service
|
||||||
*/
|
*/
|
||||||
fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService, forceAccept: Boolean = false) {}
|
suspend fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService, forceAccept: Boolean = false) {}
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright (c) 2019 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright (c) 2020 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright (c) 2019 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -189,7 +189,7 @@ internal class MXMegolmDecryption(
|
|||||||
* @param defaultKeysBackupService the keys backup service
|
* @param defaultKeysBackupService the keys backup service
|
||||||
* @param forceAccept if true will force to accept the forwarded key
|
* @param forceAccept if true will force to accept the forwarded key
|
||||||
*/
|
*/
|
||||||
override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService, forceAccept: Boolean) {
|
override suspend fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService, forceAccept: Boolean) {
|
||||||
Timber.tag(loggerTag.value).v("onRoomKeyEvent(${event.getSenderKey()})")
|
Timber.tag(loggerTag.value).v("onRoomKeyEvent(${event.getSenderKey()})")
|
||||||
var exportFormat = false
|
var exportFormat = false
|
||||||
val roomKeyContent = event.getDecryptedContent()?.toModel<RoomKeyContent>() ?: return
|
val roomKeyContent = event.getDecryptedContent()?.toModel<RoomKeyContent>() ?: return
|
||||||
@ -360,6 +360,6 @@ internal class MXMegolmDecryption(
|
|||||||
*/
|
*/
|
||||||
fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
|
fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
|
||||||
Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey")
|
Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey")
|
||||||
newSessionListener?.onNewSession(roomId, senderKey, sessionId)
|
newSessionListener?.onNewSession(roomId, sessionId)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright (c) 2019 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
@ -184,7 +184,9 @@ internal class MXMegolmEncryption(
|
|||||||
trusted = true
|
trusted = true
|
||||||
)
|
)
|
||||||
|
|
||||||
defaultKeysBackupService.maybeBackupKeys()
|
cryptoCoroutineScope.launch {
|
||||||
|
defaultKeysBackupService.maybeBackupKeys()
|
||||||
|
}
|
||||||
|
|
||||||
return MXOutboundSessionInfo(
|
return MXOutboundSessionInfo(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
@ -21,7 +21,8 @@ import androidx.work.BackoffPolicy
|
|||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||||
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
@ -35,6 +36,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerif
|
|||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.isLocallyVerified
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.isLocallyVerified
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.api.util.fromBase64
|
import org.matrix.android.sdk.api.util.fromBase64
|
||||||
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
import org.matrix.android.sdk.internal.crypto.DeviceListManager
|
||||||
@ -48,8 +50,6 @@ import org.matrix.android.sdk.internal.di.UserId
|
|||||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.task.TaskThread
|
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
|
||||||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
||||||
import org.matrix.android.sdk.internal.util.logLimit
|
import org.matrix.android.sdk.internal.util.logLimit
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||||
@ -127,7 +127,9 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Recover local trust in case private key are there?
|
// Recover local trust in case private key are there?
|
||||||
setUserKeysAsTrusted(myUserId, checkUserTrust(myUserId).isVerified())
|
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||||
|
setUserKeysAsTrusted(myUserId, checkUserTrust(myUserId).isVerified())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
// Mmm this kind of a big issue
|
// Mmm this kind of a big issue
|
||||||
@ -152,40 +154,30 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
* - Sign the keys and upload them
|
* - Sign the keys and upload them
|
||||||
* - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures.
|
* - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures.
|
||||||
*/
|
*/
|
||||||
override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback<Unit>) {
|
override suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
|
||||||
Timber.d("## CrossSigning initializeCrossSigning")
|
Timber.d("## CrossSigning initializeCrossSigning")
|
||||||
|
|
||||||
val params = InitializeCrossSigningTask.Params(
|
val params = InitializeCrossSigningTask.Params(
|
||||||
interactiveAuthInterceptor = uiaInterceptor
|
interactiveAuthInterceptor = uiaInterceptor
|
||||||
)
|
)
|
||||||
initializeCrossSigningTask.configureWith(params) {
|
val data = initializeCrossSigningTask
|
||||||
this.callbackThread = TaskThread.CRYPTO
|
.execute(params)
|
||||||
this.callback = object : MatrixCallback<InitializeCrossSigningTask.Result> {
|
val crossSigningInfo = MXCrossSigningInfo(
|
||||||
override fun onFailure(failure: Throwable) {
|
myUserId,
|
||||||
Timber.e(failure, "Error in initializeCrossSigning()")
|
listOf(data.masterKeyInfo, data.userKeyInfo, data.selfSignedKeyInfo),
|
||||||
callback.onFailure(failure)
|
true
|
||||||
}
|
)
|
||||||
|
withContext(coroutineDispatchers.crypto) {
|
||||||
override fun onSuccess(data: InitializeCrossSigningTask.Result) {
|
cryptoStore.setMyCrossSigningInfo(crossSigningInfo)
|
||||||
val crossSigningInfo = MXCrossSigningInfo(
|
setUserKeysAsTrusted(myUserId, true)
|
||||||
myUserId,
|
cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK)
|
||||||
listOf(data.masterKeyInfo, data.userKeyInfo, data.selfSignedKeyInfo),
|
crossSigningOlm.masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) }
|
||||||
true
|
crossSigningOlm.userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) }
|
||||||
)
|
crossSigningOlm.selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) }
|
||||||
cryptoStore.setMyCrossSigningInfo(crossSigningInfo)
|
}
|
||||||
setUserKeysAsTrusted(myUserId, true)
|
|
||||||
cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK)
|
|
||||||
crossSigningOlm.masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) }
|
|
||||||
crossSigningOlm.userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) }
|
|
||||||
crossSigningOlm.selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) }
|
|
||||||
|
|
||||||
callback.onSuccess(Unit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSecretMSKGossip(mskPrivateKey: String) {
|
override suspend fun onSecretMSKGossip(mskPrivateKey: String) {
|
||||||
Timber.i("## CrossSigning - onSecretSSKGossip")
|
Timber.i("## CrossSigning - onSecretSSKGossip")
|
||||||
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also {
|
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also {
|
||||||
Timber.e("## CrossSigning - onSecretMSKGossip() received secret but public key is not known")
|
Timber.e("## CrossSigning - onSecretMSKGossip() received secret but public key is not known")
|
||||||
@ -212,7 +204,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSecretSSKGossip(sskPrivateKey: String) {
|
override suspend fun onSecretSSKGossip(sskPrivateKey: String) {
|
||||||
Timber.i("## CrossSigning - onSecretSSKGossip")
|
Timber.i("## CrossSigning - onSecretSSKGossip")
|
||||||
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also {
|
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also {
|
||||||
Timber.e("## CrossSigning - onSecretSSKGossip() received secret but public key is not known")
|
Timber.e("## CrossSigning - onSecretSSKGossip() received secret but public key is not known")
|
||||||
@ -239,7 +231,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSecretUSKGossip(uskPrivateKey: String) {
|
override suspend fun onSecretUSKGossip(uskPrivateKey: String) {
|
||||||
Timber.i("## CrossSigning - onSecretUSKGossip")
|
Timber.i("## CrossSigning - onSecretUSKGossip")
|
||||||
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also {
|
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also {
|
||||||
Timber.e("## CrossSigning - onSecretUSKGossip() received secret but public key is not knwow ")
|
Timber.e("## CrossSigning - onSecretUSKGossip() received secret but public key is not knwow ")
|
||||||
@ -265,7 +257,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkTrustFromPrivateKeys(
|
override suspend fun checkTrustFromPrivateKeys(
|
||||||
masterKeyPrivateKey: String?,
|
masterKeyPrivateKey: String?,
|
||||||
uskKeyPrivateKey: String?,
|
uskKeyPrivateKey: String?,
|
||||||
sskPrivateKey: String?
|
sskPrivateKey: String?
|
||||||
@ -328,7 +320,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!masterKeyIsTrusted || !userKeyIsTrusted || !selfSignedKeyIsTrusted) {
|
if (!masterKeyIsTrusted || !userKeyIsTrusted || !selfSignedKeyIsTrusted) {
|
||||||
return UserTrustResult.KeysNotTrusted(mxCrossSigningInfo)
|
return UserTrustResult.Failure("Keys not trusted $mxCrossSigningInfo") // UserTrustResult.KeysNotTrusted(mxCrossSigningInfo)
|
||||||
} else {
|
} else {
|
||||||
cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
|
cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
|
||||||
val checkSelfTrust = checkSelfTrust()
|
val checkSelfTrust = checkSelfTrust()
|
||||||
@ -354,18 +346,22 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
* └──▶ USK ────────────┘
|
* └──▶ USK ────────────┘
|
||||||
* .
|
* .
|
||||||
*/
|
*/
|
||||||
override fun isUserTrusted(otherUserId: String): Boolean {
|
override suspend fun isUserTrusted(otherUserId: String): Boolean {
|
||||||
return cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() == true
|
return withContext(coroutineDispatchers.io) {
|
||||||
|
cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() == true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isCrossSigningVerified(): Boolean {
|
override suspend fun isCrossSigningVerified(): Boolean {
|
||||||
return checkSelfTrust().isVerified()
|
return withContext(coroutineDispatchers.io) {
|
||||||
|
checkSelfTrust().isVerified()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will not force a download of the key, but will verify signatures trust chain.
|
* Will not force a download of the key, but will verify signatures trust chain.
|
||||||
*/
|
*/
|
||||||
override fun checkUserTrust(otherUserId: String): UserTrustResult {
|
override suspend fun checkUserTrust(otherUserId: String): UserTrustResult {
|
||||||
Timber.v("## CrossSigning checkUserTrust for $otherUserId")
|
Timber.v("## CrossSigning checkUserTrust for $otherUserId")
|
||||||
if (otherUserId == myUserId) {
|
if (otherUserId == myUserId) {
|
||||||
return checkSelfTrust()
|
return checkSelfTrust()
|
||||||
@ -380,17 +376,17 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
return checkOtherMSKTrusted(myCrossSigningInfo, cryptoStore.getCrossSigningInfo(otherUserId))
|
return checkOtherMSKTrusted(myCrossSigningInfo, cryptoStore.getCrossSigningInfo(otherUserId))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkOtherMSKTrusted(myCrossSigningInfo: MXCrossSigningInfo?, otherInfo: MXCrossSigningInfo?): UserTrustResult {
|
override fun checkOtherMSKTrusted(myCrossSigningInfo: MXCrossSigningInfo?, otherInfo: MXCrossSigningInfo?): UserTrustResult {
|
||||||
val myUserKey = myCrossSigningInfo?.userKey()
|
val myUserKey = myCrossSigningInfo?.userKey()
|
||||||
?: return UserTrustResult.CrossSigningNotConfigured(myUserId)
|
?: return UserTrustResult.CrossSigningNotConfigured(myUserId)
|
||||||
|
|
||||||
if (!myCrossSigningInfo.isTrusted()) {
|
if (!myCrossSigningInfo.isTrusted()) {
|
||||||
return UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
|
return UserTrustResult.Failure("Keys not trusted $myCrossSigningInfo") // UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's get the other user master key
|
// Let's get the other user master key
|
||||||
val otherMasterKey = otherInfo?.masterKey()
|
val otherMasterKey = otherInfo?.masterKey()
|
||||||
?: return UserTrustResult.UnknownCrossSignatureInfo(otherInfo?.userId ?: "")
|
?: return UserTrustResult.Failure("Unknown MSK for ${otherInfo?.userId}") // UserTrustResult.UnknownCrossSignatureInfo(otherInfo?.userId ?: "")
|
||||||
|
|
||||||
val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures
|
val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures
|
||||||
?.get(myUserId) // Signatures made by me
|
?.get(myUserId) // Signatures made by me
|
||||||
@ -398,7 +394,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
|
|
||||||
if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) {
|
if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) {
|
||||||
Timber.d("## CrossSigning checkUserTrust false for ${otherInfo.userId}, not signed by my UserSigningKey")
|
Timber.d("## CrossSigning checkUserTrust false for ${otherInfo.userId}, not signed by my UserSigningKey")
|
||||||
return UserTrustResult.KeyNotSigned(otherMasterKey)
|
return UserTrustResult.Failure("MSK not signed by my USK $otherMasterKey") // UserTrustResult.KeyNotSigned(otherMasterKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that Alice USK signature of Bob MSK is valid
|
// Check that Alice USK signature of Bob MSK is valid
|
||||||
@ -409,7 +405,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
otherMasterKey.canonicalSignable()
|
otherMasterKey.canonicalSignable()
|
||||||
)
|
)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
return UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey)
|
return UserTrustResult.Failure("Invalid signature $masterKeySignaturesMadeByMyUserKey") // UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
return UserTrustResult.Success
|
return UserTrustResult.Success
|
||||||
@ -424,7 +420,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
return checkSelfTrust(myCrossSigningInfo, cryptoStore.getUserDeviceList(myUserId))
|
return checkSelfTrust(myCrossSigningInfo, cryptoStore.getUserDeviceList(myUserId))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkSelfTrust(myCrossSigningInfo: MXCrossSigningInfo?, myDevices: List<CryptoDeviceInfo>?): UserTrustResult {
|
override fun checkSelfTrust(myCrossSigningInfo: MXCrossSigningInfo?, myDevices: List<CryptoDeviceInfo>?): UserTrustResult {
|
||||||
// Special case when it's me,
|
// Special case when it's me,
|
||||||
// I have to check that MSK -> USK -> SSK
|
// I have to check that MSK -> USK -> SSK
|
||||||
// and that MSK is trusted (i know the private key, or is signed by a trusted device)
|
// and that MSK is trusted (i know the private key, or is signed by a trusted device)
|
||||||
@ -473,7 +469,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isMaterKeyTrusted) {
|
if (!isMaterKeyTrusted) {
|
||||||
return UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
|
return UserTrustResult.Failure("Keys not trusted $myCrossSigningInfo") // UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
val myUserKey = myCrossSigningInfo.userKey()
|
val myUserKey = myCrossSigningInfo.userKey()
|
||||||
@ -485,7 +481,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
|
|
||||||
if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
|
if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
|
||||||
Timber.d("## CrossSigning checkUserTrust false for $myUserId, USK not signed by MSK")
|
Timber.d("## CrossSigning checkUserTrust false for $myUserId, USK not signed by MSK")
|
||||||
return UserTrustResult.KeyNotSigned(myUserKey)
|
return UserTrustResult.Failure("USK not signed by MSK") // UserTrustResult.KeyNotSigned(myUserKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that Alice USK signature of Alice MSK is valid
|
// Check that Alice USK signature of Alice MSK is valid
|
||||||
@ -496,7 +492,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
myUserKey.canonicalSignable()
|
myUserKey.canonicalSignable()
|
||||||
)
|
)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
return UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey)
|
return UserTrustResult.Failure("Invalid MSK signature of USK") // UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
val mySSKey = myCrossSigningInfo.selfSigningKey()
|
val mySSKey = myCrossSigningInfo.selfSigningKey()
|
||||||
@ -508,7 +504,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
|
|
||||||
if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
|
if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
|
||||||
Timber.d("## CrossSigning checkUserTrust false for $myUserId, SSK not signed by MSK")
|
Timber.d("## CrossSigning checkUserTrust false for $myUserId, SSK not signed by MSK")
|
||||||
return UserTrustResult.KeyNotSigned(mySSKey)
|
return UserTrustResult.Failure("SSK not signed by MSK") // UserTrustResult.KeyNotSigned(mySSKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that Alice USK signature of Alice MSK is valid
|
// Check that Alice USK signature of Alice MSK is valid
|
||||||
@ -519,26 +515,32 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
mySSKey.canonicalSignable()
|
mySSKey.canonicalSignable()
|
||||||
)
|
)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
return UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey)
|
return UserTrustResult.Failure("Invalid signature $ssKeySignaturesMadeByMyMasterKey") // UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
return UserTrustResult.Success
|
return UserTrustResult.Success
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
|
override suspend fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
|
||||||
return cryptoStore.getCrossSigningInfo(otherUserId)
|
return withContext(coroutineDispatchers.io) {
|
||||||
|
cryptoStore.getCrossSigningInfo(otherUserId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
|
override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
|
||||||
return cryptoStore.getLiveCrossSigningInfo(userId)
|
return cryptoStore.getLiveCrossSigningInfo(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
|
override suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
|
||||||
return cryptoStore.getMyCrossSigningInfo()
|
return withContext(coroutineDispatchers.io) {
|
||||||
|
cryptoStore.getMyCrossSigningInfo()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
|
override suspend fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
|
||||||
return cryptoStore.getCrossSigningPrivateKeys()
|
return withContext(coroutineDispatchers.io) {
|
||||||
|
cryptoStore.getCrossSigningPrivateKeys()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> {
|
override fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> {
|
||||||
@ -555,24 +557,20 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
cryptoStore.getCrossSigningPrivateKeys()?.allKnown().orFalse()
|
cryptoStore.getCrossSigningPrivateKeys()?.allKnown().orFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) {
|
override suspend fun trustUser(otherUserId: String) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
Timber.d("## CrossSigning - Mark user $otherUserId as trusted ")
|
Timber.d("## CrossSigning - Mark user $otherUserId as trusted ")
|
||||||
// We should have this user keys
|
// We should have this user keys
|
||||||
val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey()
|
val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey()
|
||||||
if (otherMasterKeys == null) {
|
if (otherMasterKeys == null) {
|
||||||
callback.onFailure(Throwable("## CrossSigning - Other master signing key is not known"))
|
throw Throwable("## CrossSigning - Other master signing key is not known")
|
||||||
return@launch
|
|
||||||
}
|
}
|
||||||
val myKeys = getUserCrossSigningKeys(myUserId)
|
val myKeys = getUserCrossSigningKeys(myUserId)
|
||||||
if (myKeys == null) {
|
?: throw Throwable("## CrossSigning - CrossSigning is not setup for this account")
|
||||||
callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account"))
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey
|
val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey
|
||||||
if (userPubKey == null || crossSigningOlm.userPkSigning == null) {
|
if (userPubKey == null || crossSigningOlm.userPkSigning == null) {
|
||||||
callback.onFailure(Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey"))
|
throw Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey")
|
||||||
return@launch
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign the other MasterKey with our UserSigning key
|
// Sign the other MasterKey with our UserSigning key
|
||||||
@ -580,12 +578,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
Map::class.java,
|
Map::class.java,
|
||||||
otherMasterKeys.signalableJSONDictionary()
|
otherMasterKeys.signalableJSONDictionary()
|
||||||
).let { crossSigningOlm.userPkSigning?.sign(it) }
|
).let { crossSigningOlm.userPkSigning?.sign(it) }
|
||||||
|
?: // race??
|
||||||
if (newSignature == null) {
|
throw Throwable("## CrossSigning - Failed to sign")
|
||||||
// race??
|
|
||||||
callback.onFailure(Throwable("## CrossSigning - Failed to sign"))
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
cryptoStore.setUserKeysAsTrusted(otherUserId, true)
|
cryptoStore.setUserKeysAsTrusted(otherUserId, true)
|
||||||
|
|
||||||
@ -593,10 +587,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
val uploadQuery = UploadSignatureQueryBuilder()
|
val uploadQuery = UploadSignatureQueryBuilder()
|
||||||
.withSigningKeyInfo(otherMasterKeys.copyForSignature(myUserId, userPubKey, newSignature))
|
.withSigningKeyInfo(otherMasterKeys.copyForSignature(myUserId, userPubKey, newSignature))
|
||||||
.build()
|
.build()
|
||||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
|
||||||
this.executionThread = TaskThread.CRYPTO
|
uploadSignaturesTask.execute(UploadSignaturesTask.Params(uploadQuery))
|
||||||
this.callback = callback
|
|
||||||
}.executeBy(taskExecutor)
|
|
||||||
|
|
||||||
// Local echo for device cross trust, to avoid having to wait for a notification of key change
|
// Local echo for device cross trust, to avoid having to wait for a notification of key change
|
||||||
cryptoStore.getUserDeviceList(otherUserId)?.forEach { device ->
|
cryptoStore.getUserDeviceList(otherUserId)?.forEach { device ->
|
||||||
@ -607,8 +599,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun markMyMasterKeyAsTrusted() {
|
override suspend fun markMyMasterKeyAsTrusted() {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
|
cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
|
||||||
checkSelfTrust()
|
checkSelfTrust()
|
||||||
// re-verify all trusts
|
// re-verify all trusts
|
||||||
@ -616,35 +608,26 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun trustDevice(deviceId: String, callback: MatrixCallback<Unit>) {
|
override suspend fun trustDevice(deviceId: String) {
|
||||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
withContext(coroutineDispatchers.crypto) {
|
||||||
// This device should be yours
|
// This device should be yours
|
||||||
val device = cryptoStore.getUserDevice(myUserId, deviceId)
|
val device = cryptoStore.getUserDevice(myUserId, deviceId)
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours"))
|
throw IllegalArgumentException("This device [$deviceId] is not known, or not yours")
|
||||||
return@launch
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val myKeys = getUserCrossSigningKeys(myUserId)
|
val myKeys = getUserCrossSigningKeys(myUserId)
|
||||||
if (myKeys == null) {
|
?: throw Throwable("CrossSigning is not setup for this account")
|
||||||
callback.onFailure(Throwable("CrossSigning is not setup for this account"))
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey
|
val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey
|
||||||
if (ssPubKey == null || crossSigningOlm.selfSigningPkSigning == null) {
|
if (ssPubKey == null || crossSigningOlm.selfSigningPkSigning == null) {
|
||||||
callback.onFailure(Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey"))
|
throw Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey")
|
||||||
return@launch
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign with self signing
|
// Sign with self signing
|
||||||
val newSignature = crossSigningOlm.selfSigningPkSigning?.sign(device.canonicalSignable())
|
val newSignature = crossSigningOlm.selfSigningPkSigning?.sign(device.canonicalSignable())
|
||||||
|
?: throw Throwable("Failed to sign")
|
||||||
|
|
||||||
if (newSignature == null) {
|
|
||||||
// race??
|
|
||||||
callback.onFailure(Throwable("Failed to sign"))
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
val toUpload = device.copy(
|
val toUpload = device.copy(
|
||||||
signatures = mapOf(
|
signatures = mapOf(
|
||||||
myUserId
|
myUserId
|
||||||
@ -658,14 +641,16 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
val uploadQuery = UploadSignatureQueryBuilder()
|
val uploadQuery = UploadSignatureQueryBuilder()
|
||||||
.withDeviceInfo(toUpload)
|
.withDeviceInfo(toUpload)
|
||||||
.build()
|
.build()
|
||||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
uploadSignaturesTask.execute(UploadSignaturesTask.Params(uploadQuery))
|
||||||
this.executionThread = TaskThread.CRYPTO
|
|
||||||
this.callback = callback
|
|
||||||
}.executeBy(taskExecutor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
|
override suspend fun shieldForGroup(userIds: List<String>): RoomEncryptionTrustLevel {
|
||||||
|
// Not used in kotlin SDK?
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
|
||||||
val otherDevice = cryptoStore.getUserDevice(otherUserId, otherDeviceId)
|
val otherDevice = cryptoStore.getUserDevice(otherUserId, otherDeviceId)
|
||||||
?: return DeviceTrustResult.UnknownDevice(otherDeviceId)
|
?: return DeviceTrustResult.UnknownDevice(otherDeviceId)
|
||||||
|
|
||||||
@ -787,10 +772,12 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
|
|
||||||
override fun onUsersDeviceUpdate(userIds: List<String>) {
|
override fun onUsersDeviceUpdate(userIds: List<String>) {
|
||||||
Timber.d("## CrossSigning - onUsersDeviceUpdate for users: ${userIds.logLimit()}")
|
Timber.d("## CrossSigning - onUsersDeviceUpdate for users: ${userIds.logLimit()}")
|
||||||
checkTrustAndAffectedRoomShields(userIds)
|
runBlocking {
|
||||||
|
checkTrustAndAffectedRoomShields(userIds)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkTrustAndAffectedRoomShields(userIds: List<String>) {
|
override suspend fun checkTrustAndAffectedRoomShields(userIds: List<String>) {
|
||||||
Timber.d("## CrossSigning - checkTrustAndAffectedRoomShields for users: ${userIds.logLimit()}")
|
Timber.d("## CrossSigning - checkTrustAndAffectedRoomShields for users: ${userIds.logLimit()}")
|
||||||
val workerParams = UpdateTrustWorker.Params(
|
val workerParams = UpdateTrustWorker.Params(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
@ -808,7 +795,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
.enqueue()
|
.enqueue()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
|
private suspend fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
|
||||||
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
|
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
|
||||||
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
|
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
|
||||||
// If it's me, recheck trust of all users and devices?
|
// If it's me, recheck trust of all users and devices?
|
||||||
@ -818,7 +805,10 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
outgoingKeyRequestManager.onSelfCrossSigningTrustChanged(trusted)
|
outgoingKeyRequestManager.onSelfCrossSigningTrustChanged(trusted)
|
||||||
cryptoStore.updateUsersTrust {
|
cryptoStore.updateUsersTrust {
|
||||||
users.add(it)
|
users.add(it)
|
||||||
checkUserTrust(it).isVerified()
|
// called within a real transaction, has to block
|
||||||
|
runBlocking {
|
||||||
|
checkUserTrust(it).isVerified()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
users.forEach {
|
users.forEach {
|
@ -15,11 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.matrix.android.sdk.internal.crypto.crosssigning
|
package org.matrix.android.sdk.internal.crypto.crosssigning
|
||||||
|
|
||||||
import android.util.Base64
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
internal fun CryptoDeviceInfo.canonicalSignable(): String {
|
internal fun CryptoDeviceInfo.canonicalSignable(): String {
|
||||||
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
|
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
|
||||||
@ -28,15 +26,3 @@ internal fun CryptoDeviceInfo.canonicalSignable(): String {
|
|||||||
internal fun CryptoCrossSigningKey.canonicalSignable(): String {
|
internal fun CryptoCrossSigningKey.canonicalSignable(): String {
|
||||||
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
|
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode the base 64. Return null in case of bad format. Should be used when parsing received data from external source
|
|
||||||
*/
|
|
||||||
internal fun String.fromBase64Safe(): ByteArray? {
|
|
||||||
return try {
|
|
||||||
Base64.decode(this, Base64.DEFAULT)
|
|
||||||
} catch (throwable: Throwable) {
|
|
||||||
Timber.e(throwable, "Unable to decode base64 string")
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright (c) 2020 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -22,7 +22,9 @@ import com.squareup.moshi.JsonClass
|
|||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.UserTrustResult
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.UserTrustResult
|
||||||
import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified
|
||||||
@ -68,7 +70,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
|
|||||||
val filename: String? = null
|
val filename: String? = null
|
||||||
) : SessionWorkerParams
|
) : SessionWorkerParams
|
||||||
|
|
||||||
@Inject lateinit var crossSigningService: DefaultCrossSigningService
|
@Inject lateinit var crossSigningService: CrossSigningService
|
||||||
|
|
||||||
// It breaks the crypto store contract, but we need to batch things :/
|
// It breaks the crypto store contract, but we need to batch things :/
|
||||||
@CryptoDatabase
|
@CryptoDatabase
|
||||||
@ -174,9 +176,9 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
|
|||||||
?.devices
|
?.devices
|
||||||
|
|
||||||
val trustMap = devicesEntities?.associateWith { device ->
|
val trustMap = devicesEntities?.associateWith { device ->
|
||||||
// get up to date from DB has could have been updated
|
runBlocking {
|
||||||
val otherInfo = getCrossSigningInfo(cryptoRealm, userId)
|
crossSigningService.checkDeviceTrust(userId, device.deviceId ?: "", CryptoMapper.mapToModel(device).trustLevel?.locallyVerified)
|
||||||
crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update trust if needed
|
// Update trust if needed
|
File diff suppressed because it is too large
Load Diff
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest
|
|||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject
|
import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoReady
|
import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoReady
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,4 +32,6 @@ internal data class KeyVerificationReady(
|
|||||||
) : SendToDeviceObject, VerificationInfoReady {
|
) : SendToDeviceObject, VerificationInfoReady {
|
||||||
|
|
||||||
override fun toSendToDeviceObject() = this
|
override fun toSendToDeviceObject() = this
|
||||||
|
|
||||||
|
override fun toEventContent() = toContent()
|
||||||
}
|
}
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest
|
|||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject
|
import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoRequest
|
import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoRequest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,4 +33,6 @@ internal data class KeyVerificationRequest(
|
|||||||
) : SendToDeviceObject, VerificationInfoRequest {
|
) : SendToDeviceObject, VerificationInfoRequest {
|
||||||
|
|
||||||
override fun toSendToDeviceObject() = this
|
override fun toSendToDeviceObject() = this
|
||||||
|
|
||||||
|
override fun toEventContent() = toContent()
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user