mirror of
https://github.com/element-hq/element-android
synced 2024-11-23 09:55:40 +03:00
canonicalize
This commit is contained in:
parent
1436667e7d
commit
e70fd8e351
9 changed files with 296 additions and 21 deletions
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}"""))
|
||||
}
|
||||
}
|
|
@ -73,7 +73,6 @@ interface CryptoService {
|
|||
|
||||
fun getDeviceInfo(userId: String, deviceId: String?, callback: MatrixCallback<MXDeviceInfo?>)
|
||||
|
||||
// TODO move elsewhere
|
||||
fun reRequestRoomKeyForEvent(event: Event)
|
||||
|
||||
fun cancelRoomKeyRequest(requestBody: RoomKeyRequestBody)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<ForwardedRoomKeyContent>()!!
|
||||
|
|
|
@ -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 <T> getCanonicalJson(type: Class<T>, o: T): String {
|
||||
val adadpter = moshi.adapter<T>(type)
|
||||
val adapter = moshi.adapter<T>(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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -48,7 +48,7 @@ internal class SyncModule {
|
|||
}
|
||||
|
||||
scope(DefaultSession.SCOPE) {
|
||||
CryptoSyncHandler(get())
|
||||
CryptoSyncHandler(get(), get())
|
||||
}
|
||||
|
||||
scope(DefaultSession.SCOPE) {
|
||||
|
|
|
@ -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<String>()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue