diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkFactory.kt index 1af77869ee..03c5149e6b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/PermalinkFactory.kt @@ -19,7 +19,7 @@ package im.vector.matrix.android.api.permalinks import im.vector.matrix.android.api.session.events.model.Event /** - * Useful methods to create Matrix permalink. + * Useful methods to create Matrix permalink (matrix.to links). */ object PermalinkFactory { @@ -84,7 +84,17 @@ object PermalinkFactory { * @param id the id to escape * @return the escaped id */ - private fun escape(id: String): String { + internal fun escape(id: String): String { return id.replace("/", "%2F") } + + /** + * Unescape '/' in id + * + * @param id the id to escape + * @return the escaped id + */ + internal fun unescape(id: String): String { + return id.replace("%2F", "/") + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/Extensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/Extensions.kt new file mode 100644 index 0000000000..a2fc5e688c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/Extensions.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2020 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.crypto.verification.qrcode + +import im.vector.matrix.android.api.MatrixPatterns +import im.vector.matrix.android.api.permalinks.PermalinkFactory + +/** + * Generate an URL to generate a QR code of the form: + *
+ * https://matrix.to/#/+ */ +fun QrCodeData.toUrl(): String { + return buildString { + append(PermalinkFactory.createPermalink(userId)) + append("?request=") + append(PermalinkFactory.escape(requestId)) + append("&action=verify") + + for ((keyId, key) in keys) { + append("&key_$keyId=") + append(PermalinkFactory.escape(key)) + } + + append("&verification_algorithms=") + append(PermalinkFactory.escape(verificationAlgorithms)) + append("&verification_key=") + append(PermalinkFactory.escape(verificationKey)) + append("&other_user_key=") + append(PermalinkFactory.escape(otherUserKey)) + } +} + +fun String.toQrCodeData(): QrCodeData? { + if (!startsWith(PermalinkFactory.MATRIX_TO_URL_BASE)) { + return null + } + + val fragment = substringAfter("#") + if (fragment.isEmpty()) { + return null + } + + val safeFragment = fragment.substringBefore("?") + + // we are limiting to 2 params + val params = safeFragment + .split(MatrixPatterns.SEP_REGEX.toRegex()) + .filter { it.isNotEmpty() } + + if (params.size != 1) { + return null + } + + val userId = params.getOrNull(0) + ?.let { PermalinkFactory.unescape(it) } + ?.takeIf { MatrixPatterns.isUserId(it) } ?: return null + + val urlParams = fragment.substringAfter("?") + .split("&".toRegex()) + .filter { it.isNotEmpty() } + + val keyValues = urlParams.map { + (it.substringBefore("=") to it.substringAfter("=")) + }.toMap() + + if (keyValues["action"] != "verify") { + return null + } + + val requestId = keyValues["request"] + ?.let { PermalinkFactory.unescape(it) } + ?.takeIf { MatrixPatterns.isEventId(it) } ?: return null + val verificationAlgorithms = keyValues["verification_algorithms"] ?: return null + val verificationKey = keyValues["verification_key"] ?: return null + val otherUserKey = keyValues["other_user_key"] ?: return null + + val keys = keyValues.keys + .filter { it.startsWith("key_") } + .map { + it.substringAfter("key_") to (keyValues[it] ?: return null) + } + .toMap() + + return QrCodeData( + userId, + requestId, + keys, + verificationAlgorithms, + verificationKey, + otherUserKey + ) +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/QrCodeData.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/QrCodeData.kt new file mode 100644 index 0000000000..9b97deb7ea --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/QrCodeData.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2020 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.crypto.verification.qrcode + +/** + * Ref: https://github.com/uhoreg/matrix-doc/blob/qr_key_verification/proposals/1543-qr_code_key_verification.md#qr-code-format + */ +data class QrCodeData( + val userId: String, + // the event ID of the associated verification request event. + val requestId: String, + // key_? + * request= + * &action=verify + * &key_ = ... + * &verification_algorithms= + * &verification_key= + * &other_user_key= + *