diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/qrcode/SharedSecretTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/qrcode/SharedSecretTest.kt new file mode 100644 index 0000000000..845d6f269a --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/qrcode/SharedSecretTest.kt @@ -0,0 +1,46 @@ +/* + * 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 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 SharedSecretTest : InstrumentedTest { + + @Test + fun testSharedSecretLengthCase() { + val sharedSecret = generateSharedSecret() + + sharedSecret.length shouldBe 43 + } + + @Test + fun testSharedDiffCase() { + val sharedSecret1 = generateSharedSecret() + val sharedSecret2 = generateSharedSecret() + + sharedSecret1 shouldNotBeEqualTo sharedSecret2 + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 878280295b..72a19900ba 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.internal.crypto.crosssigning -import android.util.Base64 import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.Credentials @@ -83,40 +82,43 @@ internal class DefaultCrossSigningService @Inject constructor( Timber.i("## CrossSigning - Found Existing self signed keys") Timber.i("## CrossSigning - Checking if private keys are known") - cryptoStore.getCrossSigningPrivateKeys()?.let { privateKeyinfo -> - privateKeyinfo.master?.let { privateKey -> - val keySeed = Base64.decode(privateKey, Base64.NO_PADDING) - val pkSigning = OlmPkSigning() - if (pkSigning.initWithSeed(keySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { - masterPkSigning = pkSigning - Timber.i("## CrossSigning - Loading master key success") - } else { - Timber.w("## CrossSigning - Public master key does not match the private key") - // TODO untrust - } - } - privateKeyinfo.user?.let { privateKey -> - val keySeed = Base64.decode(privateKey, Base64.NO_PADDING) - val pkSigning = OlmPkSigning() - if (pkSigning.initWithSeed(keySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { - userPkSigning = pkSigning - Timber.i("## CrossSigning - Loading User Signing key success") - } else { - Timber.w("## CrossSigning - Public User key does not match the private key") - // TODO untrust - } - } - privateKeyinfo.selfSigned?.let { privateKey -> - val keySeed = Base64.decode(privateKey, Base64.NO_PADDING) - val pkSigning = OlmPkSigning() - if (pkSigning.initWithSeed(keySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { - selfSigningPkSigning = pkSigning - Timber.i("## CrossSigning - Loading Self Signing key success") - } else { - Timber.w("## CrossSigning - Public Self Signing key does not match the private key") - // TODO untrust - } - } + cryptoStore.getCrossSigningPrivateKeys()?.let { privateKeysInfo -> + privateKeysInfo.master + ?.fromBase64NoPadding() + ?.let { privateKeySeed -> + val pkSigning = OlmPkSigning() + if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { + masterPkSigning = pkSigning + Timber.i("## CrossSigning - Loading master key success") + } else { + Timber.w("## CrossSigning - Public master key does not match the private key") + // TODO untrust + } + } + privateKeysInfo.user + ?.fromBase64NoPadding() + ?.let { privateKeySeed -> + val pkSigning = OlmPkSigning() + if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { + userPkSigning = pkSigning + Timber.i("## CrossSigning - Loading User Signing key success") + } else { + Timber.w("## CrossSigning - Public User key does not match the private key") + // TODO untrust + } + } + privateKeysInfo.selfSigned + ?.fromBase64NoPadding() + ?.let { privateKeySeed -> + val pkSigning = OlmPkSigning() + if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { + selfSigningPkSigning = pkSigning + Timber.i("## CrossSigning - Loading Self Signing key success") + } else { + Timber.w("## CrossSigning - Public Self Signing key does not match the private key") + // TODO untrust + } + } } } } catch (e: Throwable) { @@ -365,7 +367,9 @@ internal class DefaultCrossSigningService @Inject constructor( // Is the master key trusted // 1) check if I know the private key - val masterPrivateKey = cryptoStore.getCrossSigningPrivateKeys()?.master + val masterPrivateKey = cryptoStore.getCrossSigningPrivateKeys() + ?.master + ?.fromBase64NoPadding() var isMaterKeyTrusted = false if (masterPrivateKey != null) { @@ -373,11 +377,12 @@ internal class DefaultCrossSigningService @Inject constructor( var olmPkSigning: OlmPkSigning? = null try { olmPkSigning = OlmPkSigning() - val expectedPK = olmPkSigning.initWithSeed(Base64.decode(masterPrivateKey, Base64.NO_PADDING)) + val expectedPK = olmPkSigning.initWithSeed(masterPrivateKey) isMaterKeyTrusted = myMasterKey.unpaddedBase64PublicKey == expectedPK } catch (failure: Throwable) { - olmPkSigning?.releaseSigning() + Timber.e(failure) } + olmPkSigning?.releaseSigning() } else { // Maybe it's signed by a locally trusted device? myMasterKey.signatures?.get(myUserId)?.forEach { (key, value) -> diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/Extensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/Extensions.kt index 3d0f68d4f6..6ffc341881 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/Extensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/Extensions.kt @@ -28,6 +28,10 @@ fun CryptoCrossSigningKey.canonicalSignable(): String { return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary()) } -fun ByteArray.toBase64NoPadding() : String? { - return Base64.encodeToString(this, Base64.NO_PADDING) +fun ByteArray.toBase64NoPadding(): String { + return Base64.encodeToString(this, Base64.NO_PADDING or Base64.NO_WRAP) +} + +fun String.fromBase64NoPadding(): ByteArray { + return Base64.decode(this, Base64.NO_PADDING or Base64.NO_WRAP) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/PrivateKeysInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/PrivateKeysInfo.kt index e2add0beff..a10b6d2645 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/PrivateKeysInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/PrivateKeysInfo.kt @@ -1,3 +1,19 @@ +/* + * 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.store data class PrivateKeysInfo( diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/SharedSecret.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/SharedSecret.kt new file mode 100644 index 0000000000..d319ebd88c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/qrcode/SharedSecret.kt @@ -0,0 +1,29 @@ +/* + * 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.internal.crypto.crosssigning.toBase64NoPadding +import java.security.SecureRandom + +fun generateSharedSecret(): String { + val secureRandom = SecureRandom() + + // 256 bits long + val secretBytes = ByteArray(32) + secureRandom.nextBytes(secretBytes) + return secretBytes.toBase64NoPadding() +}