diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt index 50e1f332a2..596d8d3e5d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/securestorage/SharedSecretStorageService.kt @@ -92,7 +92,7 @@ interface SharedSecretStorageService { * @param secret The secret contents. * @param keys The list of (ID,privateKey) of the keys to use to encrypt the secret. */ - fun storeSecret(name: String, secretBase64: String, keys: List>, callback: MatrixCallback) + fun storeSecret(name: String, secretBase64: String, keys: List, callback: MatrixCallback) /** * Use this call to determine which SSSSKeySpec to use for requesting secret @@ -110,4 +110,9 @@ interface SharedSecretStorageService { fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback) fun checkShouldBeAbleToAccessSecrets(secretNames: List, keyId: String?) : IntegrityResult + + data class KeyRef( + val keyId: String?, + val keySpec: SsssKeySpec? + ) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt index 5a6a0bc303..50b7bd7984 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/secrets/DefaultSharedSecretStorageService.kt @@ -180,17 +180,17 @@ internal class DefaultSharedSecretStorageService @Inject constructor( return getKey(keyId) } - override fun storeSecret(name: String, secretBase64: String, keys: List>, callback: MatrixCallback) { + override fun storeSecret(name: String, secretBase64: String, keys: List, callback: MatrixCallback) { cryptoCoroutineScope.launch(coroutineDispatchers.main) { val encryptedContents = HashMap() try { keys.forEach { - val keyId = it.first + val keyId = it.keyId // encrypt the content when (val key = keyId?.let { getKey(keyId) } ?: getDefaultKey()) { is KeyInfoResult.Success -> { if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_AES_HMAC_SHA2) { - encryptAesHmacSha2(it.second!!, name, secretBase64).let { + encryptAesHmacSha2(it.keySpec!!, name, secretBase64).let { encryptedContents[key.keyInfo.id] = it } } else { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt index 46aa872f06..477389a081 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/RoomVerificationUpdateTask.kt @@ -84,7 +84,7 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor( Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} type: ${event.getClearType()}") // Relates to is not encrypted - val relatesTo = event.content.toModel()?.relatesTo?.eventId + val relatesToEventId = event.content.toModel()?.relatesTo?.eventId if (event.senderId == userId) { // If it's send from me, we need to keep track of Requests or Start @@ -105,8 +105,8 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor( event.getClearContent().toModel()?.let { if (it.fromDevice != deviceId) { // The verification is started from another device - Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesTo ") - relatesTo?.let { txId -> transactionsHandledByOtherDevice.add(txId) } + Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ") + relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } params.verificationService.onRoomRequestHandledByOtherDevice(event) } } @@ -114,13 +114,13 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor( event.getClearContent().toModel()?.let { if (it.fromDevice != deviceId) { // The verification is started from another device - Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesTo ") - relatesTo?.let { txId -> transactionsHandledByOtherDevice.add(txId) } + Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ") + relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } params.verificationService.onRoomRequestHandledByOtherDevice(event) } } } else if (EventType.KEY_VERIFICATION_CANCEL == event.getClearType() || EventType.KEY_VERIFICATION_DONE == event.getClearType()) { - relatesTo?.let { + relatesToEventId?.let { transactionsHandledByOtherDevice.remove(it) params.verificationService.onRoomRequestHandledByOtherDevice(event) } @@ -130,9 +130,9 @@ internal class DefaultRoomVerificationUpdateTask @Inject constructor( return@forEach } - if (relatesTo != null && transactionsHandledByOtherDevice.contains(relatesTo)) { + if (relatesToEventId != null && transactionsHandledByOtherDevice.contains(relatesToEventId)) { // Ignore this event, it is directed to another of my devices - Timber.v("## SAS Verification live observer: Ignore Transaction handled by other device tid:$relatesTo ") + Timber.v("## SAS Verification live observer: Ignore Transaction handled by other device tid:$relatesToEventId ") return@forEach } when (event.getClearType()) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tools/HkdfSha256.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tools/HkdfSha256.kt index 60a2c4473e..4a24e054ac 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tools/HkdfSha256.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tools/HkdfSha256.kt @@ -1,27 +1,12 @@ /* * 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. - */ - -/* * Copyright (C) 2015 Square, Inc. * * 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 + * 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, diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index 31a70a22e0..ec6d5cfcf0 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ android:supportsRtl="true" android:theme="@style/AppTheme.Light" tools:replace="android:allowBackup"> + @@ -56,6 +57,7 @@ + @@ -71,7 +73,7 @@ android:value=".features.home.HomeActivity" /> - + @@ -95,7 +97,6 @@ - @@ -103,14 +104,15 @@ - + + + @@ -134,6 +137,7 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".features.home.HomeActivity" /> + @@ -146,17 +150,25 @@ android:theme="@style/AppTheme.AttachmentsPreview" /> + + + android:exported="false" /> + + + + android:exported="false" /> + + + { - setResult(Activity.RESULT_CANCELED) finish() } is SharedSecureStorageViewEvent.Error -> { @@ -95,7 +85,6 @@ class SharedSecureStorageActivity : SimpleFragmentActivity() { .setCancelable(false) .setPositiveButton(R.string.ok) { _, _ -> if (it.dismiss) { - setResult(Activity.RESULT_CANCELED) finish() } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt index 4fc82da8ca..3d13cdb946 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -24,9 +24,9 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.listeners.ProgressListener import im.vector.matrix.android.api.session.Session -import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec import im.vector.matrix.android.api.session.securestorage.IntegrityResult import im.vector.matrix.android.api.session.securestorage.KeyInfoResult +import im.vector.matrix.android.api.session.securestorage.RawBytesKeySpec import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding import im.vector.matrix.android.internal.util.awaitCallback import im.vector.riotx.R @@ -69,81 +69,87 @@ class SharedSecureStorageViewModel @AssistedInject constructor( override fun handle(action: SharedSecureStorageAction) = withState { when (action) { - is SharedSecureStorageAction.TogglePasswordVisibility -> { - setState { - copy( - passphraseVisible = !passphraseVisible - ) - } - } - is SharedSecureStorageAction.Cancel -> { - _viewEvents.post(SharedSecureStorageViewEvent.Dismiss) - } - is SharedSecureStorageAction.SubmitPassphrase -> { - val decryptedSecretMap = HashMap() - GlobalScope.launch(Dispatchers.IO) { - runCatching { - _viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading) - val passphrase = action.passphrase - val keyInfoResult = session.sharedSecretStorageService.getDefaultKey() - if (!keyInfoResult.isSuccess()) { - _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) - _viewEvents.post(SharedSecureStorageViewEvent.Error("Cannot find ssss key")) - return@launch - } - val keyInfo = (keyInfoResult as KeyInfoResult.Success).keyInfo + is SharedSecureStorageAction.TogglePasswordVisibility -> handleTogglePasswordVisibility() + is SharedSecureStorageAction.Cancel -> handleCancel() + is SharedSecureStorageAction.SubmitPassphrase -> handleSubmitPassphrase(action) + } + } - _viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState( - WaitingViewData( - message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message), - isIndeterminate = true - ) - )) - val keySpec = RawBytesKeySpec.fromPassphrase( - passphrase, - keyInfo.content.passphrase?.salt ?: "", - keyInfo.content.passphrase?.iterations ?: 0, - // TODO - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - _viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState( - WaitingViewData( - message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message), - isIndeterminate = false, - progress = progress, - progressTotal = total - ) - )) - } - } + private fun handleSubmitPassphrase(action: SharedSecureStorageAction.SubmitPassphrase) { + val decryptedSecretMap = HashMap() + GlobalScope.launch(Dispatchers.IO) { + runCatching { + _viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading) + val passphrase = action.passphrase + val keyInfoResult = session.sharedSecretStorageService.getDefaultKey() + if (!keyInfoResult.isSuccess()) { + _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) + _viewEvents.post(SharedSecureStorageViewEvent.Error("Cannot find ssss key")) + return@launch + } + val keyInfo = (keyInfoResult as KeyInfoResult.Success).keyInfo + + _viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState( + WaitingViewData( + message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message), + isIndeterminate = true ) - - withContext(Dispatchers.IO) { - args.requestedSecrets.forEach { - val res = awaitCallback { callback -> - session.sharedSecretStorageService.getSecret( - name = it, - keyId = keyInfo.id, - secretKey = keySpec, - callback = callback) - } - decryptedSecretMap[it] = res + )) + val keySpec = RawBytesKeySpec.fromPassphrase( + passphrase, + keyInfo.content.passphrase?.salt ?: "", + keyInfo.content.passphrase?.iterations ?: 0, + // TODO + object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + _viewEvents.post(SharedSecureStorageViewEvent.UpdateLoadingState( + WaitingViewData( + message = stringProvider.getString(R.string.keys_backup_restoring_computing_key_waiting_message), + isIndeterminate = false, + progress = progress, + progressTotal = total + ) + )) } } - }.fold({ - _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) - val safeForIntentCypher = ByteArrayOutputStream().also { - it.use { - session.securelyStoreObject(decryptedSecretMap as Map, args.resultKeyStoreAlias, it) - } - }.toByteArray().toBase64NoPadding() - _viewEvents.post(SharedSecureStorageViewEvent.FinishSuccess(safeForIntentCypher)) - }, { - _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) - _viewEvents.post(SharedSecureStorageViewEvent.InlineError(it.localizedMessage)) - }) + ) + + withContext(Dispatchers.IO) { + args.requestedSecrets.forEach { + val res = awaitCallback { callback -> + session.sharedSecretStorageService.getSecret( + name = it, + keyId = keyInfo.id, + secretKey = keySpec, + callback = callback) + } + decryptedSecretMap[it] = res + } } - } + }.fold({ + _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) + val safeForIntentCypher = ByteArrayOutputStream().also { + it.use { + session.securelyStoreObject(decryptedSecretMap as Map, args.resultKeyStoreAlias, it) + } + }.toByteArray().toBase64NoPadding() + _viewEvents.post(SharedSecureStorageViewEvent.FinishSuccess(safeForIntentCypher)) + }, { + _viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading) + _viewEvents.post(SharedSecureStorageViewEvent.InlineError(it.localizedMessage)) + }) + } + } + + private fun handleCancel() { + _viewEvents.post(SharedSecureStorageViewEvent.Dismiss) + } + + private fun handleTogglePasswordVisibility() { + setState { + copy( + passphraseVisible = !passphraseVisible + ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt index 27df0393c4..f7a5e7c1bc 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt @@ -19,15 +19,8 @@ package im.vector.riotx.features.crypto.quads import android.os.Bundle import android.view.View import android.view.inputmethod.EditorInfo -import android.widget.Button -import android.widget.EditText -import android.widget.ImageView -import android.widget.TextView -import butterknife.BindView -import butterknife.OnClick import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.withState -import com.google.android.material.textfield.TextInputLayout import com.jakewharton.rxbinding3.view.clicks import com.jakewharton.rxbinding3.widget.editorActionEvents import com.jakewharton.rxbinding3.widget.textChanges @@ -35,6 +28,7 @@ import im.vector.riotx.R import im.vector.riotx.core.extensions.showPassword import im.vector.riotx.core.platform.VectorBaseFragment import io.reactivex.android.schedulers.AndroidSchedulers +import kotlinx.android.synthetic.main.fragment_ssss_access_from_passphrase.* import me.gujun.android.span.span import java.util.concurrent.TimeUnit @@ -44,36 +38,10 @@ class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() { val sharedViewModel: SharedSecureStorageViewModel by activityViewModel() - @BindView(R.id.ssss_restore_with_passphrase_warning_text) - lateinit var warningText: TextView - - @BindView(R.id.ssss_restore_with_passphrase_warning_reason) - lateinit var reasonText: TextView - - @BindView(R.id.ssss_passphrase_enter_til) - lateinit var mPassphraseInputLayout: TextInputLayout - - @BindView(R.id.ssss_passphrase_enter_edittext) - lateinit var mPassphraseTextEdit: EditText - - @BindView(R.id.ssss_view_show_password) - lateinit var mPassphraseReveal: ImageView - - @BindView(R.id.ssss_passphrase_submit) - lateinit var submitButton: Button - - @BindView(R.id.ssss_passphrase_cancel) - lateinit var cancelButton: Button - - @OnClick(R.id.ssss_view_show_password) - fun toggleVisibilityMode() { - sharedViewModel.handle(SharedSecureStorageAction.TogglePasswordVisibility) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - warningText.text = span { + ssss_restore_with_passphrase_warning_text.text = span { span(getString(R.string.enter_secret_storage_passphrase_warning)) { textStyle = "bold" } @@ -81,9 +49,9 @@ class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() { +getString(R.string.enter_secret_storage_passphrase_warning_text) } - reasonText.text = getString(R.string.enter_secret_storage_passphrase_reason_verify) + ssss_restore_with_passphrase_warning_reason.text = getString(R.string.enter_secret_storage_passphrase_reason_verify) - mPassphraseTextEdit.editorActionEvents() + ssss_passphrase_enter_edittext.editorActionEvents() .debounce(300, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe { @@ -93,22 +61,22 @@ class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() { } .disposeOnDestroyView() - mPassphraseTextEdit.textChanges() + ssss_passphrase_enter_edittext.textChanges() .subscribe { - mPassphraseInputLayout.error = null - submitButton.isEnabled = it.isNotBlank() + ssss_passphrase_enter_til.error = null + ssss_passphrase_submit.isEnabled = it.isNotBlank() } .disposeOnDestroyView() sharedViewModel.observeViewEvents { when (it) { is SharedSecureStorageViewEvent.InlineError -> { - mPassphraseInputLayout.error = it.message + ssss_passphrase_enter_til.error = it.message } } } - submitButton.clicks() + ssss_passphrase_submit.clicks() .debounce(300, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe { @@ -116,25 +84,33 @@ class SharedSecuredStoragePassphraseFragment : VectorBaseFragment() { } .disposeOnDestroyView() - cancelButton.clicks() + ssss_passphrase_cancel.clicks() .debounce(300, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe { sharedViewModel.handle(SharedSecureStorageAction.Cancel) } .disposeOnDestroyView() + + ssss_view_show_password.clicks() + .debounce(300, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { + sharedViewModel.handle(SharedSecureStorageAction.TogglePasswordVisibility) + } + .disposeOnDestroyView() } fun submit() { - val text = mPassphraseTextEdit.text.toString() + val text = ssss_passphrase_enter_edittext.text.toString() if (text.isBlank()) return // Should not reach this point as button disabled - submitButton.isEnabled = false + ssss_passphrase_submit.isEnabled = false sharedViewModel.handle(SharedSecureStorageAction.SubmitPassphrase(text)) } override fun invalidate() = withState(sharedViewModel) { state -> val shouldBeVisible = state.passphraseVisible - mPassphraseTextEdit.showPassword(shouldBeVisible) - mPassphraseReveal.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black) + ssss_passphrase_enter_edittext.showPassword(shouldBeVisible) + ssss_view_show_password.setImageResource(if (shouldBeVisible) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black) } } diff --git a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt index 61599c0f14..154849c227 100644 --- a/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt +++ b/vector/src/main/java/im/vector/riotx/features/crypto/verification/VerificationBottomSheet.kt @@ -106,8 +106,7 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment() { .setTitle(getString(R.string.dialog_title_error)) .setMessage(it.errorMessage) .setCancelable(false) - .setPositiveButton(R.string.ok) { _, _ -> - } + .setPositiveButton(R.string.ok, null) .show() Unit } diff --git a/vector/src/main/res/values/dimens.xml b/vector/src/main/res/values/dimens.xml index 411945c8ed..665d1819f7 100644 --- a/vector/src/main/res/values/dimens.xml +++ b/vector/src/main/res/values/dimens.xml @@ -32,6 +32,5 @@ 280dp - 16dp \ No newline at end of file diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 31cd015051..6c2caedd01 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2151,5 +2151,4 @@ Not all features in Riot are implemented in RiotX yet. Main missing (and coming No Connectivity to the server has been lost - ShareSecureStorageActivity