mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-24 02:15:46 +03:00
QrCodeV2 WIP (al tests passing)
This commit is contained in:
parent
35b10daef1
commit
e00d3ef63d
7 changed files with 576 additions and 0 deletions
|
@ -0,0 +1,293 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 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 androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
|
import org.amshove.kluent.shouldBeNull
|
||||||
|
import org.amshove.kluent.shouldEqual
|
||||||
|
import org.amshove.kluent.shouldEqualTo
|
||||||
|
import org.amshove.kluent.shouldNotBeNull
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
class QrCodeV2Test : InstrumentedTest {
|
||||||
|
|
||||||
|
private val qrCode1 = QrCodeDataV2.VerifyingAnotherUser(
|
||||||
|
transactionId = "MaTransaction",
|
||||||
|
userMasterCrossSigningPublicKey = "ktEwcUP6su1xh+GuE+CYkQ3H6W/DIl+ybHFdaEOrolU",
|
||||||
|
otherUserMasterCrossSigningPublicKey = "TXluZKTZLvSRWOTPlOqLq534bA+/K4zLFKSu9cGLQaU",
|
||||||
|
sharedSecret = "MTIzNDU2Nzg"
|
||||||
|
)
|
||||||
|
|
||||||
|
private val value1 = "MATRIX\u0002\u0000\u0000\u000DMaTransaction\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢UMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥12345678"
|
||||||
|
|
||||||
|
private val qrCode2 = QrCodeDataV2.SelfVerifyingMasterKeyTrusted(
|
||||||
|
transactionId = "MaTransaction",
|
||||||
|
userMasterCrossSigningPublicKey = "ktEwcUP6su1xh+GuE+CYkQ3H6W/DIl+ybHFdaEOrolU",
|
||||||
|
otherDeviceKey = "TXluZKTZLvSRWOTPlOqLq534bA+/K4zLFKSu9cGLQaU",
|
||||||
|
sharedSecret = "MTIzNDU2Nzg"
|
||||||
|
)
|
||||||
|
|
||||||
|
private val value2 = "MATRIX\u0002\u0001\u0000\u000DMaTransaction\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢UMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥12345678"
|
||||||
|
|
||||||
|
private val qrCode3 = QrCodeDataV2.SelfVerifyingMasterKeyNotTrusted(
|
||||||
|
transactionId = "MaTransaction",
|
||||||
|
deviceKey = "TXluZKTZLvSRWOTPlOqLq534bA+/K4zLFKSu9cGLQaU",
|
||||||
|
userMasterCrossSigningPublicKey = "ktEwcUP6su1xh+GuE+CYkQ3H6W/DIl+ybHFdaEOrolU",
|
||||||
|
sharedSecret = "MTIzNDU2Nzg"
|
||||||
|
)
|
||||||
|
|
||||||
|
private val value3 = "MATRIX\u0002\u0002\u0000\u000DMaTransactionMynd¤Ù.ô\u0091XäÏ\u0094ê\u008B«\u009Døl\u000F¿+\u008CË\u0014¤®õÁ\u008BA¥\u0092Ñ0qCú²íq\u0087á®\u0013à\u0098\u0091\u000DÇéoÃ\"_²lq]hC«¢U12345678"
|
||||||
|
|
||||||
|
private val sharedSecretByteArray = "12345678".toByteArray(Charsets.ISO_8859_1)
|
||||||
|
|
||||||
|
// 4d 79 6e 64 a4 d9 2e f4 91 58 e4 cf 94 ea 8b ab 9d f8 6c 0f bf 2b 8c cb 14 a4 ae f5 c1 8b 41 a5
|
||||||
|
private val tlx_byteArray = ByteArray(32) {
|
||||||
|
when (it) {
|
||||||
|
0 -> 0x4D.toByte()
|
||||||
|
1 -> 0x79.toByte()
|
||||||
|
2 -> 0x6E.toByte()
|
||||||
|
3 -> 0x64.toByte()
|
||||||
|
4 -> 0xA4.toByte()
|
||||||
|
5 -> 0xD9.toByte()
|
||||||
|
6 -> 0x2E.toByte()
|
||||||
|
7 -> 0xF4.toByte()
|
||||||
|
8 -> 0x91.toByte()
|
||||||
|
9 -> 0x58.toByte()
|
||||||
|
10 -> 0xE4.toByte()
|
||||||
|
11 -> 0xCF.toByte()
|
||||||
|
12 -> 0x94.toByte()
|
||||||
|
13 -> 0xEA.toByte()
|
||||||
|
14 -> 0x8B.toByte()
|
||||||
|
15 -> 0xAB.toByte()
|
||||||
|
16 -> 0x9D.toByte()
|
||||||
|
17 -> 0xF8.toByte()
|
||||||
|
18 -> 0x6C.toByte()
|
||||||
|
19 -> 0x0F.toByte()
|
||||||
|
20 -> 0xBF.toByte()
|
||||||
|
21 -> 0x2B.toByte()
|
||||||
|
22 -> 0x8C.toByte()
|
||||||
|
23 -> 0xCB.toByte()
|
||||||
|
24 -> 0x14.toByte()
|
||||||
|
25 -> 0xA4.toByte()
|
||||||
|
26 -> 0xAE.toByte()
|
||||||
|
27 -> 0xF5.toByte()
|
||||||
|
28 -> 0xC1.toByte()
|
||||||
|
29 -> 0x8B.toByte()
|
||||||
|
30 -> 0x41.toByte()
|
||||||
|
else -> 0xA5.toByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 92 d1 30 71 43 fa b2 ed 71 87 e1 ae 13 e0 98 91 0d c7 e9 6f c3 22 5f b2 6c 71 5d 68 43 ab a2 55
|
||||||
|
private val kte_byteArray = ByteArray(32) {
|
||||||
|
when (it) {
|
||||||
|
0 -> 0x92.toByte()
|
||||||
|
1 -> 0xd1.toByte()
|
||||||
|
2 -> 0x30.toByte()
|
||||||
|
3 -> 0x71.toByte()
|
||||||
|
4 -> 0x43.toByte()
|
||||||
|
5 -> 0xfa.toByte()
|
||||||
|
6 -> 0xb2.toByte()
|
||||||
|
7 -> 0xed.toByte()
|
||||||
|
8 -> 0x71.toByte()
|
||||||
|
9 -> 0x87.toByte()
|
||||||
|
10 -> 0xe1.toByte()
|
||||||
|
11 -> 0xae.toByte()
|
||||||
|
12 -> 0x13.toByte()
|
||||||
|
13 -> 0xe0.toByte()
|
||||||
|
14 -> 0x98.toByte()
|
||||||
|
15 -> 0x91.toByte()
|
||||||
|
16 -> 0x0d.toByte()
|
||||||
|
17 -> 0xc7.toByte()
|
||||||
|
18 -> 0xe9.toByte()
|
||||||
|
19 -> 0x6f.toByte()
|
||||||
|
20 -> 0xc3.toByte()
|
||||||
|
21 -> 0x22.toByte()
|
||||||
|
22 -> 0x5f.toByte()
|
||||||
|
23 -> 0xb2.toByte()
|
||||||
|
24 -> 0x6c.toByte()
|
||||||
|
25 -> 0x71.toByte()
|
||||||
|
26 -> 0x5d.toByte()
|
||||||
|
27 -> 0x68.toByte()
|
||||||
|
28 -> 0x43.toByte()
|
||||||
|
29 -> 0xab.toByte()
|
||||||
|
30 -> 0xa2.toByte()
|
||||||
|
else -> 0x55.toByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testEncoding1() {
|
||||||
|
qrCode1.toEncodedString() shouldEqual value1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testEncoding2() {
|
||||||
|
qrCode2.toEncodedString() shouldEqual value2
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testEncoding3() {
|
||||||
|
qrCode3.toEncodedString() shouldEqual value3
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSymmetry1() {
|
||||||
|
qrCode1.toEncodedString().toQrCodeDataV2() shouldEqual qrCode1
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSymmetry2() {
|
||||||
|
qrCode2.toEncodedString().toQrCodeDataV2() shouldEqual qrCode2
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSymmetry3() {
|
||||||
|
qrCode3.toEncodedString().toQrCodeDataV2() shouldEqual qrCode3
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCase1() {
|
||||||
|
val url = qrCode1.toEncodedString()
|
||||||
|
|
||||||
|
val byteArray = url.toByteArray(Charsets.ISO_8859_1)
|
||||||
|
checkHeader(byteArray)
|
||||||
|
|
||||||
|
// Mode
|
||||||
|
byteArray[7] shouldEqualTo 0
|
||||||
|
|
||||||
|
checkSizeAndTransaction(byteArray)
|
||||||
|
|
||||||
|
compareArray(byteArray.copyOfRange(23, 23 + 32), kte_byteArray)
|
||||||
|
compareArray(byteArray.copyOfRange(23 + 32, 23 + 64), tlx_byteArray)
|
||||||
|
|
||||||
|
compareArray(byteArray.copyOfRange(23 + 64, byteArray.size), sharedSecretByteArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCase2() {
|
||||||
|
val url = qrCode2.toEncodedString()
|
||||||
|
|
||||||
|
val byteArray = url.toByteArray(Charsets.ISO_8859_1)
|
||||||
|
checkHeader(byteArray)
|
||||||
|
|
||||||
|
// Mode
|
||||||
|
byteArray[7] shouldEqualTo 1
|
||||||
|
|
||||||
|
checkSizeAndTransaction(byteArray)
|
||||||
|
compareArray(byteArray.copyOfRange(23, 23 + 32), kte_byteArray)
|
||||||
|
compareArray(byteArray.copyOfRange(23 + 32, 23 + 64), tlx_byteArray)
|
||||||
|
|
||||||
|
compareArray(byteArray.copyOfRange(23 + 64, byteArray.size), sharedSecretByteArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCase3() {
|
||||||
|
val url = qrCode3.toEncodedString()
|
||||||
|
|
||||||
|
val byteArray = url.toByteArray(Charsets.ISO_8859_1)
|
||||||
|
checkHeader(byteArray)
|
||||||
|
|
||||||
|
// Mode
|
||||||
|
byteArray[7] shouldEqualTo 2
|
||||||
|
|
||||||
|
checkSizeAndTransaction(byteArray)
|
||||||
|
compareArray(byteArray.copyOfRange(23, 23 + 32), tlx_byteArray)
|
||||||
|
compareArray(byteArray.copyOfRange(23 + 32, 23 + 64), kte_byteArray)
|
||||||
|
|
||||||
|
compareArray(byteArray.copyOfRange(23 + 64, byteArray.size), sharedSecretByteArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error cases
|
||||||
|
@Test
|
||||||
|
fun testErrorHeader() {
|
||||||
|
value1.replace("MATRIX", "MOTRIX").toQrCodeDataV2().shouldBeNull()
|
||||||
|
value1.replace("MATRIX", "MATRI").toQrCodeDataV2().shouldBeNull()
|
||||||
|
value1.replace("MATRIX", "").toQrCodeDataV2().shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testErrorVersion() {
|
||||||
|
value1.replace("MATRIX\u0002", "MATRIX\u0000").toQrCodeDataV2().shouldBeNull()
|
||||||
|
value1.replace("MATRIX\u0002", "MATRIX\u0001").toQrCodeDataV2().shouldBeNull()
|
||||||
|
value1.replace("MATRIX\u0002", "MATRIX\u0003").toQrCodeDataV2().shouldBeNull()
|
||||||
|
value1.replace("MATRIX\u0002", "MATRIX").toQrCodeDataV2().shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testErrorSecretTooShort() {
|
||||||
|
value1.replace("12345678", "1234567").toQrCodeDataV2().shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testErrorNoTransactionNoKeyNoSecret() {
|
||||||
|
// But keep transaction length
|
||||||
|
"MATRIX\u0002\u0000\u0000\u000D".toQrCodeDataV2().shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testErrorNoKeyNoSecret() {
|
||||||
|
"MATRIX\u0002\u0000\u0000\u000DMaTransaction".toQrCodeDataV2().shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testErrorTransactionLengthTooShort() {
|
||||||
|
// In this case, the secret will be longer, so this is not an error, but it will lead to keys mismatch
|
||||||
|
value1.replace("\u000DMaTransaction", "\u000CMaTransaction").toQrCodeDataV2().shouldNotBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testErrorTransactionLengthTooBig() {
|
||||||
|
value1.replace("\u000DMaTransaction", "\u000EMaTransaction").toQrCodeDataV2().shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compareArray(actual: ByteArray, expected: ByteArray) {
|
||||||
|
actual.size shouldEqual expected.size
|
||||||
|
|
||||||
|
for (i in actual.indices) {
|
||||||
|
actual[i] shouldEqualTo expected[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkHeader(byteArray: ByteArray) {
|
||||||
|
// MATRIX
|
||||||
|
byteArray[0] shouldEqualTo 'M'.toByte()
|
||||||
|
byteArray[1] shouldEqualTo 'A'.toByte()
|
||||||
|
byteArray[2] shouldEqualTo 'T'.toByte()
|
||||||
|
byteArray[3] shouldEqualTo 'R'.toByte()
|
||||||
|
byteArray[4] shouldEqualTo 'I'.toByte()
|
||||||
|
byteArray[5] shouldEqualTo 'X'.toByte()
|
||||||
|
|
||||||
|
// Version
|
||||||
|
byteArray[6] shouldEqualTo 2
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkSizeAndTransaction(byteArray: ByteArray) {
|
||||||
|
// Size
|
||||||
|
byteArray[8] shouldEqualTo 0
|
||||||
|
byteArray[9] shouldEqualTo 13
|
||||||
|
|
||||||
|
// Transaction
|
||||||
|
byteArray.copyOfRange(10, 10 + "MaTransaction".length).toString(Charsets.ISO_8859_1) shouldEqual "MaTransaction"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 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 androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import im.vector.matrix.android.InstrumentedTest
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.amshove.kluent.shouldNotBeEqualTo
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
class SharedSecretV2Test : InstrumentedTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSharedSecretLengthCase() {
|
||||||
|
repeat(100) {
|
||||||
|
generateSharedSecretV2().length shouldBe 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testSharedDiffCase() {
|
||||||
|
val sharedSecret1 = generateSharedSecretV2()
|
||||||
|
val sharedSecret2 = generateSharedSecretV2()
|
||||||
|
|
||||||
|
sharedSecret1 shouldNotBeEqualTo sharedSecret2
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,6 +45,7 @@ private const val ENCODING = "utf-8"
|
||||||
* &other_device_key=WqSVLkBREDACTEDBsfszdvsdBEvefqsdcsfBvsfcsFb
|
* &other_device_key=WqSVLkBREDACTEDBsfszdvsdBEvefqsdcsfBvsfcsFb
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
|
// @Deprecated(message = "Use QrCodeDataV2")
|
||||||
fun QrCodeData.toUrl(): String {
|
fun QrCodeData.toUrl(): String {
|
||||||
return buildString {
|
return buildString {
|
||||||
append(PermalinkFactory.createPermalink(userId))
|
append(PermalinkFactory.createPermalink(userId))
|
||||||
|
@ -72,6 +73,7 @@ fun QrCodeData.toUrl(): String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Deprecated(message = "Use QrCodeDataV2")
|
||||||
fun String.toQrCodeData(): QrCodeData? {
|
fun String.toQrCodeData(): QrCodeData? {
|
||||||
if (!startsWith(PermalinkFactory.MATRIX_TO_URL_BASE)) {
|
if (!startsWith(PermalinkFactory.MATRIX_TO_URL_BASE)) {
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 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.internal.crypto.crosssigning.fromBase64NoPadding
|
||||||
|
import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
|
|
||||||
|
// MATRIX
|
||||||
|
private val prefix = "MATRIX".toByteArray(Charsets.ISO_8859_1)
|
||||||
|
|
||||||
|
fun QrCodeDataV2.toEncodedString(): String {
|
||||||
|
var result = ByteArray(0)
|
||||||
|
|
||||||
|
// MATRIX
|
||||||
|
for (i in prefix.indices) {
|
||||||
|
result += prefix[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version
|
||||||
|
result += 2
|
||||||
|
|
||||||
|
// Mode
|
||||||
|
result += when (this) {
|
||||||
|
is QrCodeDataV2.VerifyingAnotherUser -> 0
|
||||||
|
is QrCodeDataV2.SelfVerifyingMasterKeyTrusted -> 1
|
||||||
|
is QrCodeDataV2.SelfVerifyingMasterKeyNotTrusted -> 2
|
||||||
|
}.toByte()
|
||||||
|
|
||||||
|
// TransactionId length
|
||||||
|
val length = transactionId.length
|
||||||
|
result += ((length and 0xFF00) shr 8).toByte()
|
||||||
|
result += length.toByte()
|
||||||
|
|
||||||
|
// TransactionId
|
||||||
|
transactionId.forEach {
|
||||||
|
result += it.toByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys
|
||||||
|
firstKey.fromBase64NoPadding().forEach {
|
||||||
|
result += it
|
||||||
|
}
|
||||||
|
secondKey.fromBase64NoPadding().forEach {
|
||||||
|
result += it
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secret
|
||||||
|
sharedSecret.fromBase64NoPadding().forEach {
|
||||||
|
result += it
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString(Charsets.ISO_8859_1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.toQrCodeDataV2(): QrCodeDataV2? {
|
||||||
|
val byteArray = toByteArray(Charsets.ISO_8859_1)
|
||||||
|
|
||||||
|
// Size should be min 6 + 1 + 1 + 2 + ? + 32 + 32 + ? = 74 + transactionLength + secretLength
|
||||||
|
|
||||||
|
// Check header
|
||||||
|
// MATRIX
|
||||||
|
if (byteArray.size < 10) return null
|
||||||
|
|
||||||
|
for (i in prefix.indices) {
|
||||||
|
if (byteArray[i] != prefix[i]) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cursor = prefix.size // 6
|
||||||
|
|
||||||
|
// Version
|
||||||
|
if (byteArray[cursor] != 2.toByte()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
cursor++
|
||||||
|
|
||||||
|
// Get mode
|
||||||
|
val mode = byteArray[cursor].toInt()
|
||||||
|
cursor++
|
||||||
|
|
||||||
|
// Get transaction length
|
||||||
|
val transactionLength = (byteArray[cursor].toInt() shr 8) + byteArray[cursor + 1].toInt()
|
||||||
|
|
||||||
|
cursor++
|
||||||
|
cursor++
|
||||||
|
|
||||||
|
val secretLength = byteArray.size - 74 - transactionLength
|
||||||
|
|
||||||
|
// ensure the secret length is 8 bytes min
|
||||||
|
if (secretLength < 8) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val transactionId = byteArray.copyOfRange(cursor, cursor + transactionLength).toString(Charsets.ISO_8859_1)
|
||||||
|
cursor += transactionLength
|
||||||
|
val key1 = byteArray.copyOfRange(cursor, cursor + 32).toBase64NoPadding()
|
||||||
|
cursor += 32
|
||||||
|
val key2 = byteArray.copyOfRange(cursor, cursor + 32).toBase64NoPadding()
|
||||||
|
cursor += 32
|
||||||
|
val secret = byteArray.copyOfRange(cursor, byteArray.size).toBase64NoPadding()
|
||||||
|
|
||||||
|
return when (mode) {
|
||||||
|
0 -> QrCodeDataV2.VerifyingAnotherUser(transactionId, key1, key2, secret)
|
||||||
|
1 -> QrCodeDataV2.SelfVerifyingMasterKeyTrusted(transactionId, key1, key2, secret)
|
||||||
|
2 -> QrCodeDataV2.SelfVerifyingMasterKeyNotTrusted(transactionId, key1, key2, secret)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ 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
|
* Ref: https://github.com/uhoreg/matrix-doc/blob/qr_key_verification/proposals/1543-qr_code_key_verification.md#qr-code-format
|
||||||
*/
|
*/
|
||||||
|
//@Deprecated(message = "Use QrCodeDataV2")
|
||||||
data class QrCodeData(
|
data class QrCodeData(
|
||||||
val userId: String,
|
val userId: String,
|
||||||
// Request Id. Can be an arbitrary value. In DM, it will be the event ID of the associated verification request event.
|
// Request Id. Can be an arbitrary value. In DM, it will be the event ID of the associated verification request event.
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 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
|
||||||
|
*/
|
||||||
|
sealed class QrCodeDataV2(
|
||||||
|
/**
|
||||||
|
* the event ID or transaction_id of the associated verification
|
||||||
|
*/
|
||||||
|
open val transactionId: String,
|
||||||
|
/**
|
||||||
|
* First key (32 bytes, in base64 no padding)
|
||||||
|
*/
|
||||||
|
val firstKey: String,
|
||||||
|
/**
|
||||||
|
* Second key (32 bytes, in base64 no padding)
|
||||||
|
*/
|
||||||
|
val secondKey: String,
|
||||||
|
/**
|
||||||
|
* a random shared secret (in base64 no padding)
|
||||||
|
*/
|
||||||
|
open val sharedSecret: String
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* verifying another user with cross-signing
|
||||||
|
* QR code verification mode: 0x00
|
||||||
|
*/
|
||||||
|
data class VerifyingAnotherUser(
|
||||||
|
override val transactionId: String,
|
||||||
|
/**
|
||||||
|
* the user's own master cross-signing public key
|
||||||
|
*/
|
||||||
|
val userMasterCrossSigningPublicKey: String,
|
||||||
|
/**
|
||||||
|
* what the device thinks the other user's master cross-signing key is
|
||||||
|
*/
|
||||||
|
val otherUserMasterCrossSigningPublicKey: String,
|
||||||
|
override val sharedSecret: String
|
||||||
|
) : QrCodeDataV2(
|
||||||
|
transactionId,
|
||||||
|
userMasterCrossSigningPublicKey,
|
||||||
|
otherUserMasterCrossSigningPublicKey,
|
||||||
|
sharedSecret)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* self-verifying in which the current device does trust the master key
|
||||||
|
* QR code verification mode: 0x01
|
||||||
|
*/
|
||||||
|
data class SelfVerifyingMasterKeyTrusted(
|
||||||
|
override val transactionId: String,
|
||||||
|
/**
|
||||||
|
* the user's own master cross-signing public key
|
||||||
|
*/
|
||||||
|
val userMasterCrossSigningPublicKey: String,
|
||||||
|
/**
|
||||||
|
* what the device thinks the other device's device key is
|
||||||
|
*/
|
||||||
|
val otherDeviceKey: String,
|
||||||
|
override val sharedSecret: String
|
||||||
|
) : QrCodeDataV2(
|
||||||
|
transactionId,
|
||||||
|
userMasterCrossSigningPublicKey,
|
||||||
|
otherDeviceKey,
|
||||||
|
sharedSecret)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* self-verifying in which the current device does not yet trust the master key
|
||||||
|
* QR code verification mode: 0x02
|
||||||
|
*/
|
||||||
|
data class SelfVerifyingMasterKeyNotTrusted(
|
||||||
|
override val transactionId: String,
|
||||||
|
/**
|
||||||
|
* the current device's device key
|
||||||
|
*/
|
||||||
|
val deviceKey: String,
|
||||||
|
/**
|
||||||
|
* what the device thinks the user's master cross-signing key is
|
||||||
|
*/
|
||||||
|
val userMasterCrossSigningPublicKey: String,
|
||||||
|
override val sharedSecret: String
|
||||||
|
) : QrCodeDataV2(
|
||||||
|
transactionId,
|
||||||
|
deviceKey,
|
||||||
|
userMasterCrossSigningPublicKey,
|
||||||
|
sharedSecret)
|
||||||
|
}
|
|
@ -27,3 +27,12 @@ fun generateSharedSecret(): String {
|
||||||
secureRandom.nextBytes(secretBytes)
|
secureRandom.nextBytes(secretBytes)
|
||||||
return secretBytes.toBase64NoPadding()
|
return secretBytes.toBase64NoPadding()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun generateSharedSecretV2(): String {
|
||||||
|
val secureRandom = SecureRandom()
|
||||||
|
|
||||||
|
// 8 bytes long
|
||||||
|
val secretBytes = ByteArray(8)
|
||||||
|
secureRandom.nextBytes(secretBytes)
|
||||||
|
return secretBytes.toBase64NoPadding()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue