diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 4922a78e8c..c9720fbd18 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -45,9 +45,11 @@ android { debug { // Set to true to log privacy or sensible data, such as token - buildConfigField "boolean", "LOG_PRIVATE_DATA", "false" + // TODO Set to false + buildConfigField "boolean", "LOG_PRIVATE_DATA", "true" // Set to BODY instead of NONE to enable logging + //TODO Revert BODY buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.BODY" } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/util/JsonCanonicalizerTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/util/JsonCanonicalizerTest.kt new file mode 100644 index 0000000000..4e8dfdfaa3 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/util/JsonCanonicalizerTest.kt @@ -0,0 +1,154 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.util + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import im.vector.matrix.android.InstrumentedTest +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +internal class JsonCanonicalizerTest : InstrumentedTest { + + @Test + fun identityTest() { + listOf( + "{}", + """{"a":true}""", + """{"a":false}""", + """{"a":1}""", + """{"a":1.2}""", + """{"a":null}""", + """{"a":[]}""", + """{"a":["b":"c"]}""", + """{"a":["c":"b","d":"e"]}""", + """{"a":["d":"b","c":"e"]}""" + ).forEach { + assertEquals(it, + JsonCanonicalizer.canonicalize(it)) + } + } + + @Test + fun reorderTest() { + assertEquals("""{"a":true,"b":false}""", + JsonCanonicalizer.canonicalize("""{"b":false,"a":true}""")) + } + + @Test + fun realSampleTest() { + assertEquals("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX/FjTRLfySgs65ldYyomm7PIx6U"},"user_id":"@benoitx:matrix.org"}""", + JsonCanonicalizer.canonicalize("""{"algorithms":["m.megolm.v1.aes-sha2","m.olm.v1.curve25519-aes-sha2"],"device_id":"VSCUNFSOUI","user_id":"@benoitx:matrix.org","keys":{"curve25519:VSCUNFSOUI":"utyOjnhiQ73qNhi9HlN0OgWIowe5gthTS8r0r9TcJ3o","ed25519:VSCUNFSOUI":"qNhEt+Yggaajet0hX/FjTRLfySgs65ldYyomm7PIx6U"}}""")) + } + + /* ========================================================================================== + * Test from https://matrix.org/docs/spec/appendices.html#examples + * ========================================================================================== */ + + @Test + fun matrixOrg001Test() { + assertEquals("""{}""", + JsonCanonicalizer.canonicalize("""{}""")) + } + + + @Test + fun matrixOrg002Test() { + assertEquals("""{"one":1,"two":"Two"}""", + JsonCanonicalizer.canonicalize("""{ + "one": 1, + "two": "Two" +}""")) + } + + + @Test + fun matrixOrg003Test() { + assertEquals("""{"a":"1","b":"2"}""", + JsonCanonicalizer.canonicalize("""{ + "b": "2", + "a": "1" +}""")) + } + + + @Test + fun matrixOrg004Test() { + assertEquals("""{"a":"1","b":"2"}""", + JsonCanonicalizer.canonicalize("""{"b":"2","a":"1"}""")) + } + + + @Test + fun matrixOrg005Test() { + assertEquals("""{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}""", + JsonCanonicalizer.canonicalize("""{ + "auth": { + "success": true, + "mxid": "@john.doe:example.com", + "profile": { + "display_name": "John Doe", + "three_pids": [ + { + "medium": "email", + "address": "john.doe@example.org" + }, + { + "medium": "msisdn", + "address": "123456789" + } + ] + } + } +}""")) + } + + + @Test + fun matrixOrg006Test() { + assertEquals("""{"a":"日本語"}""", + JsonCanonicalizer.canonicalize("""{ + "a": "日本語" +}""")) + } + + + @Test + fun matrixOrg007Test() { + assertEquals("""{"日":1,"本":2}""", + JsonCanonicalizer.canonicalize("""{ + "本": 2, + "日": 1 +}""")) + } + + + @Test + fun matrixOrg008Test() { + assertEquals("""{"a":"日"}""", + JsonCanonicalizer.canonicalize("{\"a\": \"\u65E5\"}")) + } + + @Test + fun matrixOrg009Test() { + assertEquals("""{"a":null}""", + JsonCanonicalizer.canonicalize("""{ + "a": null +}""")) + } +} \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt index df85b2f050..073aac6141 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt @@ -73,7 +73,6 @@ interface CryptoService { fun getDeviceInfo(userId: String, deviceId: String?, callback: MatrixCallback) - // TODO move elsewhere fun reRequestRoomKeyForEvent(event: Event) fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt index ca437da0c3..ea60613315 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoManager.kt @@ -43,7 +43,10 @@ import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.model.* import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent -import im.vector.matrix.android.internal.crypto.model.rest.* +import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse +import im.vector.matrix.android.internal.crypto.model.rest.EncryptedMessage +import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse +import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.tasks.* import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService @@ -1293,11 +1296,11 @@ internal class CryptoManager( * @param event the event */ fun onToDeviceEvent(event: Event) { - mSasVerificationService.onToDeviceEvent(event) - - if (TextUtils.equals(event.type, EventType.ROOM_KEY) || TextUtils.equals(event.type, EventType.FORWARDED_ROOM_KEY)) { - getDecryptingThreadHandler().post { onRoomKeyEvent(event) } - } else if (TextUtils.equals(event.type, EventType.ROOM_KEY_REQUEST)) { + if (event.type == EventType.ROOM_KEY || event.type == EventType.FORWARDED_ROOM_KEY) { + getDecryptingThreadHandler().post { + onRoomKeyEvent(event) + } + } else if (event.type == EventType.ROOM_KEY_REQUEST) { encryptingThreadHandler.post { mIncomingRoomKeyRequestManager.onRoomKeyRequestEvent(event) } @@ -1490,7 +1493,7 @@ internal class CryptoManager( // Try to keep at most half that number on the server. This leaves the // rest of the slots free to hold keys that have been claimed from the - // server but we haven't recevied a message for. + // server but we haven't received a message for. // If we run out of slots when generating new keys then olm will // discard the oldest private keys first. This will eventually clean // out stale private keys that won't receive a message. diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 92cf5786ad..74bf33ccd1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -28,15 +28,14 @@ import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.internal.crypto.* import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult -import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXOlmSessionResult import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap -import im.vector.matrix.android.internal.crypto.CryptoManager import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent -import im.vector.matrix.android.internal.crypto.model.rest.ForwardedRoomKeyContent import im.vector.matrix.android.internal.crypto.model.event.RoomKeyContent +import im.vector.matrix.android.internal.crypto.model.rest.ForwardedRoomKeyContent import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody +import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.configureWith @@ -239,7 +238,7 @@ internal class MXMegolmDecryption : IMXDecrypting { return } - if (TextUtils.equals(event.type, EventType.FORWARDED_ROOM_KEY)) { + if (event.type == EventType.FORWARDED_ROOM_KEY) { Timber.d("## onRoomKeyEvent(), forward adding key : roomId " + roomKeyContent.roomId + " sessionId " + roomKeyContent.sessionId + " sessionKey " + roomKeyContent.sessionKey) // from " + event); val forwardedRoomKeyContent = event.content.toModel()!! diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt index eb2af1add5..f02f829450 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MoshiProvider.kt @@ -23,6 +23,7 @@ import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter import im.vector.matrix.android.internal.session.sync.model.UserAccountData import im.vector.matrix.android.internal.session.sync.model.UserAccountDataDirectMessages import im.vector.matrix.android.internal.session.sync.model.UserAccountDataFallback +import im.vector.matrix.android.internal.util.JsonCanonicalizer object MoshiProvider { @@ -48,10 +49,16 @@ object MoshiProvider { } fun getCanonicalJson(type: Class, o: T): String { - val adadpter = moshi.adapter(type) + val adapter = moshi.adapter(type) - // FIXME It is not canonical... - return adadpter.toJson(o) + val json = adapter.toJson(o) + + // Canonicalize manually + val can = JsonCanonicalizer.canonicalize(json) + + val jsonSafe = can.replace("\\/", "/") + + return jsonSafe } - } + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt index 992ad747a2..088182c34b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/CryptoSyncHandler.kt @@ -17,21 +17,24 @@ package im.vector.matrix.android.internal.session.sync import im.vector.matrix.android.internal.crypto.CryptoManager +import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.sync.model.ToDeviceSyncResponse -internal class CryptoSyncHandler(private val crypto: CryptoManager) { +internal class CryptoSyncHandler(private val cryptoManager: CryptoManager, + private val sasVerificationService: DefaultSasVerificationService) { fun handleToDevice(toDevice: ToDeviceSyncResponse) { toDevice.events?.forEach { - crypto.onToDeviceEvent(it) + sasVerificationService.onToDeviceEvent(it) + cryptoManager.onToDeviceEvent(it) } } fun onSyncCompleted(syncResponse: SyncResponse, fromToken: String?, catchingUp: Boolean) { - crypto.onSyncCompleted(syncResponse, fromToken, catchingUp) + cryptoManager.onSyncCompleted(syncResponse, fromToken, catchingUp) } } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt index d954667c6e..9ab7a259f8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt @@ -48,7 +48,7 @@ internal class SyncModule { } scope(DefaultSession.SCOPE) { - CryptoSyncHandler(get()) + CryptoSyncHandler(get(), get()) } scope(DefaultSession.SCOPE) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/JsonCanonicalizer.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/JsonCanonicalizer.kt new file mode 100644 index 0000000000..9c8c78a379 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/util/JsonCanonicalizer.kt @@ -0,0 +1,108 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.matrix.android.internal.util + +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import timber.log.Timber +import java.util.* + +/** + * Build canonical Json + * Doc: https://matrix.org/docs/spec/appendices.html#canonical-json + */ +object JsonCanonicalizer { + + fun canonicalize(json: String): String { + var can: String? = null + try { + val _json = JSONObject(json) + + can = _canonicalize(_json) + } catch (e: JSONException) { + Timber.e(e, "Unable to canonicalize") + } + + if (can == null) { + Timber.e("Error") + return json + } + + return can + } + + /** + * Canonicalize a JsonElement element + * + * @param src the src + * @return the canonicalize element + */ + private fun _canonicalize(src: Any?): String? { + // sanity check + if (null == src) { + return null + } + + when (src) { + is JSONArray -> { + // Canonicalize each element of the array + val srcArray = src as JSONArray? + val result = StringBuilder("[") + + for (i in 0 until srcArray!!.length()) { + result.append(_canonicalize(srcArray.get(i))) + if (i < srcArray.length() - 1) { + result.append(",") + } + } + + result.append("]") + + return result.toString() + } + is JSONObject -> { + // Sort the attributes by name, and the canonicalize each element of the object + val result = StringBuilder("{") + + val attributes = TreeSet() + for (entry in src.keys()) { + attributes.add(entry) + } + + for (attribute in attributes.withIndex()) { + result.append("\"") + .append(attribute.value) + .append("\"") + .append(":") + .append(_canonicalize(src[attribute.value])) + + if (attribute.index < attributes.size - 1) { + result.append(",") + } + } + + result.append("}") + + return result.toString() + } + is String -> return "\"" + src.toString() + "\"" + else -> return src.toString() + } + } + +} \ No newline at end of file