canonicalize

This commit is contained in:
Benoit Marty 2019-05-16 14:18:56 +02:00
parent 1436667e7d
commit e70fd8e351
9 changed files with 296 additions and 21 deletions

View File

@ -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"
}


View File

@ -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
}"""))
}
}

View File

@ -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)

View File

@ -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.

View File

@ -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>()!!

View File

@ -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
}

}


View File

@ -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)
}

}

View File

@ -48,7 +48,7 @@ internal class SyncModule {
}

scope(DefaultSession.SCOPE) {
CryptoSyncHandler(get())
CryptoSyncHandler(get(), get())
}

scope(DefaultSession.SCOPE) {

View File

@ -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()
}
}

}