Skip to content

Commit 7051c0c

Browse files
authored
Merge pull request #8933 from element-hq/feature/bca/fix_previously_verified_users
feat(crypto): Add support for verification violation warnings
2 parents ebfac82 + 90aed72 commit 7051c0c

File tree

11 files changed

+287
-16
lines changed

11 files changed

+287
-16
lines changed

.github/workflows/post-pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
with:
5555
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
5656
- name: Start synapse server
57-
uses: michaelkaye/setup-matrix-synapse@v1.0.4
57+
uses: michaelkaye/setup-matrix-synapse@v1.0.5
5858
with:
5959
uploadLogs: true
6060
httpPort: 8080

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
- uses: actions/setup-python@v4
5656
with:
5757
python-version: 3.8
58-
- uses: michaelkaye/setup-matrix-synapse@v1.0.4
58+
- uses: michaelkaye/setup-matrix-synapse@v1.0.5
5959
with:
6060
uploadLogs: true
6161
httpPort: 8080

changelog.d/8933.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Show a notice when a previously verified user is not anymore

matrix-sdk-android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ dependencies {
221221

222222
implementation libs.google.phonenumber
223223

224-
implementation("org.matrix.rustcomponents:crypto-android:0.4.3")
224+
implementation("org.matrix.rustcomponents:crypto-android:0.5.0")
225225
// api project(":library:rustCrypto")
226226

227227
testImplementation libs.tests.junit
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
/*
2+
* Copyright 2024 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.matrix.android.sdk.internal.crypto
18+
19+
import io.mockk.coEvery
20+
import io.mockk.every
21+
import io.mockk.mockk
22+
import kotlinx.coroutines.test.runTest
23+
import org.amshove.kluent.shouldBeEqualTo
24+
import org.junit.Test
25+
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
26+
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
27+
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
28+
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
29+
30+
class ComputeShieldForGroupUseCaseTest {
31+
32+
@Test
33+
fun shouldReturnDefaultShieldWhenNoOneIsVerified() = runTest {
34+
val mockMachine = mockk<OlmMachine> {
35+
coEvery {
36+
getIdentity("@me:localhost")
37+
} returns mockk<UserIdentities>(relaxed = true)
38+
39+
coEvery {
40+
getIdentity("@alice:localhost")
41+
} returns fakeIdentity(isVerified = false, hasVerificationViolation = false)
42+
43+
coEvery {
44+
getUserDevices("@alice:localhost")
45+
} returns listOf(fakeDevice("@alice:localhost", "A0", false))
46+
47+
coEvery {
48+
getIdentity("@bob:localhost")
49+
} returns fakeIdentity(isVerified = false, hasVerificationViolation = false)
50+
51+
coEvery {
52+
getUserDevices("@bob:localhost")
53+
} returns listOf(fakeDevice("@bob:localhost", "B0", false))
54+
55+
coEvery {
56+
getIdentity("@charly:localhost")
57+
} returns fakeIdentity(isVerified = false, hasVerificationViolation = false)
58+
59+
coEvery {
60+
getUserDevices("@charly:localhost")
61+
} returns listOf(fakeDevice("@charly:localhost", "C0", false))
62+
}
63+
64+
val computeShieldOp = ComputeShieldForGroupUseCase("@me:localhost")
65+
66+
val shield = computeShieldOp.invoke(mockMachine, listOf("@alice:localhost", "@bob:localhost", "@charly:localhost"))
67+
68+
shield shouldBeEqualTo RoomEncryptionTrustLevel.Default
69+
}
70+
71+
@Test
72+
fun shouldReturnDefaultShieldWhenVerifiedUsersHaveSecureDevices() = runTest {
73+
val mockMachine = mockk<OlmMachine> {
74+
coEvery {
75+
getIdentity("@me:localhost")
76+
} returns mockk<UserIdentities>(relaxed = true)
77+
78+
// Alice is verified
79+
coEvery {
80+
getIdentity("@alice:localhost")
81+
} returns fakeIdentity(isVerified = true, hasVerificationViolation = false)
82+
83+
coEvery {
84+
getUserDevices("@alice:localhost")
85+
} returns listOf(
86+
fakeDevice("@alice:localhost", "A0", true),
87+
fakeDevice("@alice:localhost", "A1", true)
88+
)
89+
90+
coEvery {
91+
getIdentity("@bob:localhost")
92+
} returns fakeIdentity(isVerified = false, hasVerificationViolation = false)
93+
94+
coEvery {
95+
getUserDevices("@bob:localhost")
96+
} returns listOf(fakeDevice("@bob:localhost", "B0", false))
97+
98+
coEvery {
99+
getIdentity("@charly:localhost")
100+
} returns fakeIdentity(isVerified = false, hasVerificationViolation = false)
101+
102+
coEvery {
103+
getUserDevices("@charly:localhost")
104+
} returns listOf(fakeDevice("@charly:localhost", "C0", false))
105+
}
106+
107+
val computeShieldOp = ComputeShieldForGroupUseCase("@me:localhost")
108+
109+
val shield = computeShieldOp.invoke(mockMachine, listOf("@alice:localhost", "@bob:localhost", "@charly:localhost"))
110+
111+
shield shouldBeEqualTo RoomEncryptionTrustLevel.Default
112+
}
113+
114+
@Test
115+
fun shouldReturnWarningShieldWhenPreviouslyVerifiedUsersHaveInSecureDevices() = runTest {
116+
val mockMachine = mockk<OlmMachine> {
117+
coEvery {
118+
getIdentity("@me:localhost")
119+
} returns mockk<UserIdentities>(relaxed = true)
120+
121+
// Alice is verified
122+
coEvery {
123+
getIdentity("@alice:localhost")
124+
} returns fakeIdentity(isVerified = false, hasVerificationViolation = true)
125+
126+
coEvery {
127+
getUserDevices("@alice:localhost")
128+
} returns listOf(
129+
fakeDevice("@alice:localhost", "A0", false),
130+
fakeDevice("@alice:localhost", "A1", false)
131+
)
132+
133+
coEvery {
134+
getIdentity("@bob:localhost")
135+
} returns fakeIdentity(isVerified = false, hasVerificationViolation = false)
136+
137+
coEvery {
138+
getUserDevices("@bob:localhost")
139+
} returns listOf(fakeDevice("@bob:localhost", "B0", false))
140+
141+
coEvery {
142+
getIdentity("@charly:localhost")
143+
} returns fakeIdentity(isVerified = false, hasVerificationViolation = false)
144+
145+
coEvery {
146+
getUserDevices("@charly:localhost")
147+
} returns listOf(fakeDevice("@charly:localhost", "C0", false))
148+
}
149+
150+
val computeShieldOp = ComputeShieldForGroupUseCase("@me:localhost")
151+
152+
val shield = computeShieldOp.invoke(mockMachine, listOf("@alice:localhost", "@bob:localhost", "@charly:localhost"))
153+
154+
shield shouldBeEqualTo RoomEncryptionTrustLevel.Warning
155+
}
156+
157+
@Test
158+
fun shouldReturnRedShieldWhenVerifiedUserHaveInsecureDevices() = runTest {
159+
val mockMachine = mockk<OlmMachine> {
160+
coEvery {
161+
getIdentity("@me:localhost")
162+
} returns mockk<UserIdentities>(relaxed = true)
163+
164+
// Alice is verified
165+
coEvery {
166+
getIdentity("@alice:localhost")
167+
} returns fakeIdentity(isVerified = true, hasVerificationViolation = false)
168+
169+
// And has an insecure device
170+
coEvery {
171+
getUserDevices("@alice:localhost")
172+
} returns listOf(
173+
fakeDevice("@alice:localhost", "A0", true),
174+
fakeDevice("@alice:localhost", "A1", false)
175+
)
176+
177+
coEvery {
178+
getIdentity("@bob:localhost")
179+
} returns fakeIdentity(isVerified = false, hasVerificationViolation = false)
180+
181+
coEvery {
182+
getUserDevices("@bob:localhost")
183+
} returns listOf(fakeDevice("@bob:localhost", "B0", false))
184+
185+
coEvery {
186+
getIdentity("@charly:localhost")
187+
} returns fakeIdentity(isVerified = false, hasVerificationViolation = false)
188+
189+
coEvery {
190+
getUserDevices("@charly:localhost")
191+
} returns listOf(fakeDevice("@charly:localhost", "C0", false))
192+
}
193+
194+
val computeShieldOp = ComputeShieldForGroupUseCase("@me:localhost")
195+
196+
val shield = computeShieldOp.invoke(mockMachine, listOf("@alice:localhost", "@bob:localhost", "@charly:localhost"))
197+
198+
shield shouldBeEqualTo RoomEncryptionTrustLevel.Warning
199+
}
200+
201+
@Test
202+
fun shouldReturnGreenShieldWhenAllUsersAreVerifiedAndHaveSecuredDevices() = runTest {
203+
val mockMachine = mockk<OlmMachine> {
204+
coEvery {
205+
getIdentity("@me:localhost")
206+
} returns mockk<UserIdentities>(relaxed = true)
207+
208+
// Alice is verified
209+
coEvery {
210+
getIdentity("@alice:localhost")
211+
} returns fakeIdentity(isVerified = true, hasVerificationViolation = false)
212+
213+
coEvery {
214+
getUserDevices("@alice:localhost")
215+
} returns listOf(
216+
fakeDevice("@alice:localhost", "A0", true),
217+
fakeDevice("@alice:localhost", "A1", false)
218+
)
219+
220+
coEvery {
221+
getIdentity("@bob:localhost")
222+
} returns fakeIdentity(isVerified = true, hasVerificationViolation = false)
223+
224+
coEvery {
225+
getUserDevices("@bob:localhost")
226+
} returns listOf(fakeDevice("@bob:localhost", "B0", true))
227+
228+
coEvery {
229+
getIdentity("@charly:localhost")
230+
} returns fakeIdentity(isVerified = true, hasVerificationViolation = false)
231+
232+
coEvery {
233+
getUserDevices("@charly:localhost")
234+
} returns listOf(fakeDevice("@charly:localhost", "C0", true))
235+
}
236+
237+
val computeShieldOp = ComputeShieldForGroupUseCase("@me:localhost")
238+
239+
val shield = computeShieldOp.invoke(mockMachine, listOf("@alice:localhost", "@bob:localhost", "@charly:localhost"))
240+
241+
shield shouldBeEqualTo RoomEncryptionTrustLevel.Warning
242+
}
243+
244+
companion object {
245+
internal fun fakeDevice(userId: String, deviceId: String, isSecure: Boolean) = mockk<Device>(relaxed = true) {
246+
every { toCryptoDeviceInfo() } returns CryptoDeviceInfo(
247+
deviceId = deviceId,
248+
userId = userId,
249+
trustLevel = DeviceTrustLevel(
250+
crossSigningVerified = isSecure, locallyVerified = null
251+
)
252+
)
253+
}
254+
255+
internal fun fakeIdentity(isVerified: Boolean, hasVerificationViolation: Boolean) = mockk<UserIdentities>(relaxed = true) {
256+
coEvery { toMxCrossSigningInfo() } returns mockk<MXCrossSigningInfo> {
257+
every { wasTrustedOnce } returns hasVerificationViolation
258+
every { isTrusted() } returns isVerified
259+
}
260+
}
261+
}
262+
}

matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import org.junit.Assert.assertNotNull
2424
import org.junit.Assert.assertNull
2525
import org.junit.Assert.assertTrue
2626
import org.junit.Assert.fail
27-
import org.junit.Assume
2827
import org.junit.FixMethodOrder
2928
import org.junit.Test
3029
import org.junit.runner.RunWith
@@ -202,9 +201,6 @@ class XSigningTest : InstrumentedTest {
202201
val aliceSession = cryptoTestData.firstSession
203202
val bobSession = cryptoTestData.secondSession
204203

205-
// Remove when https://github.com/matrix-org/matrix-rust-sdk/issues/1129
206-
Assume.assumeTrue("Not yet supported by rust", aliceSession.cryptoService().name() != "rust-sdk")
207-
208204
val aliceAuthParams = UserPasswordAuth(
209205
user = aliceSession.myUserId,
210206
password = TestConstants.PASSWORD

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/ComputeShieldForGroupUseCase.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ internal class ComputeShieldForGroupUseCase @Inject constructor(
2929
val myIdentity = olmMachine.getIdentity(myUserId)
3030
val allTrustedUserIds = userIds
3131
.filter { userId ->
32-
olmMachine.getIdentity(userId)?.verified() == true
32+
val identity = olmMachine.getIdentity(userId)?.toMxCrossSigningInfo()
33+
identity?.isTrusted() == true ||
34+
// Always take into account users that was previously verified but are not anymore
35+
identity?.wasTrustedOnce == true
3336
}
3437

3538
return if (allTrustedUserIds.isEmpty()) {

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GetUserIdentityUseCase.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ internal class GetUserIdentityUseCase @Inject constructor(
6666
innerMachine = innerMachine,
6767
requestSender = requestSender,
6868
coroutineDispatchers = coroutineDispatchers,
69-
verificationRequestFactory = verificationRequestFactory
69+
verificationRequestFactory = verificationRequestFactory,
70+
hasVerificationViolation = identity.hasVerificationViolation
7071
)
7172
}
7273
is InnerUserIdentity.Own -> {
@@ -89,7 +90,8 @@ internal class GetUserIdentityUseCase @Inject constructor(
8990
innerMachine = innerMachine,
9091
requestSender = requestSender,
9192
coroutineDispatchers = coroutineDispatchers,
92-
verificationRequestFactory = verificationRequestFactory
93+
verificationRequestFactory = verificationRequestFactory,
94+
hasVerificationViolation = identity.hasVerificationViolation
9395
)
9496
}
9597
null -> null

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmMachine.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ import org.matrix.rustcomponents.sdk.crypto.ShieldState
8484
import org.matrix.rustcomponents.sdk.crypto.SignatureVerification
8585
import org.matrix.rustcomponents.sdk.crypto.setLogger
8686
import timber.log.Timber
87+
import uniffi.matrix_sdk_crypto.DecryptionSettings
8788
import uniffi.matrix_sdk_crypto.LocalTrust
89+
import uniffi.matrix_sdk_crypto.TrustRequirement
8890
import java.io.File
8991
import java.nio.charset.Charset
9092
import javax.inject.Inject
@@ -450,7 +452,12 @@ internal class OlmMachine @Inject constructor(
450452
}
451453

452454
val serializedEvent = adapter.toJson(event)
453-
val decrypted = inner.decryptRoomEvent(serializedEvent, event.roomId, false, false)
455+
val decrypted = inner.decryptRoomEvent(
456+
serializedEvent, event.roomId,
457+
handleVerificationEvents = false,
458+
strictShields = false,
459+
decryptionSettings = DecryptionSettings(TrustRequirement.UNTRUSTED)
460+
)
454461

455462
val deserializationAdapter =
456463
moshi.adapter<JsonDict>(Map::class.java)

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCrossSigningService.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ internal class RustCrossSigningService @Inject constructor(
5858
* Checks that my trusted user key has signed the other user UserKey
5959
*/
6060
override suspend fun checkUserTrust(otherUserId: String): UserTrustResult {
61-
val identity = olmMachine.getIdentity(olmMachine.userId())
61+
val identity = olmMachine.getIdentity(otherUserId)
6262

6363
// While UserTrustResult has many different states, they are by the callers
6464
// converted to a boolean value immediately, thus we don't need to support

0 commit comments

Comments
 (0)