Skip to content

Commit

Permalink
Merge pull request #13456 from nextcloud/refactor/setup-dialog
Browse files Browse the repository at this point in the history
Refactor Setup Dialog
  • Loading branch information
AndyScherzinger authored Aug 24, 2024
2 parents fd09a82 + 374f5c7 commit af880a4
Show file tree
Hide file tree
Showing 15 changed files with 185 additions and 16 deletions.
4 changes: 4 additions & 0 deletions app/src/androidTest/assets/credentials.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3wSNveXIhRsKl86pUnL7\n/AIAH+IJya5vqP0lv+yCBkd728szrLRYRWxPNC4VDbzyRHBr0RWj0ibsLJvU2OeF\n5p4er1tMIGgB0AEwiuDXBBz/RrxjPdhlilq7mvvqeUS2M3t5iroIxM6VEGQrhVrb\nb3U+7c6Lt7dIHAHEVOXnZiHYhhhduEmIzbsrAZFuMjlnWXTiMhuuWBf6t1nPyCHa\noA96loWibbvIsMegC73J3Ej5sgLkz/TjlrYmv6p3RGAEs74KHfggy4Fzw9TxBAAY\nyIX0NY8Rhb10XKrOSXrvRYuL/wkJ3P5XVK/NfsuLKbrhuUjDSgKplY9xCtOSaEPJ\nVQIDAQAB\n-----END PUBLIC KEY-----",
"certificate": "-----BEGIN CERTIFICATE-----\nMIIC9DCCAdygAwIBAgIBADANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDDAhuYXJy\nYXRvcjAeFw0yNDA1MjcxMzEyNDVaFw00NDA1MjIxMzEyNDVaMBMxETAPBgNVBAMM\nCG5hcnJhdG9yMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjA2EEYeN\nc3BdVDPkJK/AWPB1kd9sWAonZt/V4sbAE6fGy4qU21xfInZQaMHyhqdMXga10juE\nJLPKuyyRz+qijASryW+WzCJ3A9QeHHO+CiLc09yuB80JRpH0oHsol6WrdO1n5zuH\nlPtAdCwi4OeRmvazfBysbP2gaUl7DxackqbMei8a0MoyDxUB11hp0tpyYAU1/sXZ\nLGh4R4q4/F2KlSeYY9D62OJ8wNTgv9AYF/HRxXxWmVftB1En/DdvVr1zJGraHiRm\nQbaEnmsSGK8QHHm4h37cfD5f7rW1WO5A8KyJKwluOIXjMfL1YijAPpNW6EHhSlfT\n5RVLCHxvrzMHewIDAQABo1MwUTAdBgNVHQ4EFgQUzT6RHEHtpdjr8N3ABJK0wpFt\n1PMwHwYDVR0jBBgwFoAUzT6RHEHtpdjr8N3ABJK0wpFt1PMwDwYDVR0TAQH/BAUw\nAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAJ1q3CSBHrLauOZAD56BeElgh/ahbegsE\nZ4w7q4FdhkixLIwe6yrMmSvpNTuxRDHUrVLXxQmN0X3Yb7BLNXnnIUfH9EozaV7p\nYjOLWD2XCfLJmpGIBVvqZhyZrTl69jkBaVHF78aj1vt+qKihHUAVnG+qGH0PFms+\nG0KyY8bNYg+2HQiSTva1kgGPUA/8nQNj3lwi+r03tgqbw88fQKRPeMUJWdh/yV9U\noBdPHt+TBsUFZQZP3lBBS9lYhDT9fNoGX12WPAEUjYNhHVX+Qdup8Mg3aUMITXXJ\nvlGsN1SknlLoN0RwBFbyH9BCzqAdEIj5qQM3YDzIIyyy6AAnswNEUg==\n-----END CERTIFICATE-----"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <[email protected]>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nextcloud.utils

import androidx.test.platform.app.InstrumentationRegistry
import com.google.gson.Gson
import com.owncloud.android.datamodel.Credentials
import com.owncloud.android.ui.dialog.setupEncryption.CertificateValidator
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.io.InputStreamReader

class CertificateValidatorTests {

private var sut: CertificateValidator? = null

@Before
fun setup() {
sut = CertificateValidator()
}

@After
fun destroy() {
sut = null
}

@Test
fun testValidateWhenGivenValidServerKeyAndCertificateShouldReturnTrue() {
val inputStream =
InstrumentationRegistry.getInstrumentation().context.assets.open("credentials.json")

val credentials = InputStreamReader(inputStream).use { reader ->
Gson().fromJson(reader, Credentials::class.java)
}

val isCertificateValid = sut?.validate(credentials.publicKey, credentials.certificate) ?: false
assert(isCertificateValid)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <[email protected]>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.owncloud.android.datamodel

data class Credentials(
val publicKey: String,
val certificate: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import androidx.test.espresso.intent.rule.IntentsTestRule
import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
import com.nextcloud.test.TestActivity
import com.owncloud.android.AbstractIT
import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment
import com.owncloud.android.utils.ScreenshotTest
import org.junit.Rule
import org.junit.Test
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/com/nextcloud/client/di/AppModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import com.owncloud.android.ui.activities.data.files.FilesRepository;
import com.owncloud.android.ui.activities.data.files.FilesServiceApiImpl;
import com.owncloud.android.ui.activities.data.files.RemoteFilesRepository;
import com.owncloud.android.ui.dialog.setupEncryption.CertificateValidator;
import com.owncloud.android.utils.theme.ViewThemeUtils;

import org.greenrobot.eventbus.EventBus;
Expand Down Expand Up @@ -255,4 +256,10 @@ UsersAndGroupsSearchConfig userAndGroupSearchConfig() {
return new UsersAndGroupsSearchConfig();
}


@Provides
@Singleton
CertificateValidator certificateValidator() {
return new CertificateValidator();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
import com.owncloud.android.ui.dialog.RenamePublicShareDialogFragment;
import com.owncloud.android.ui.dialog.SendFilesDialog;
import com.owncloud.android.ui.dialog.SendShareDialog;
import com.owncloud.android.ui.dialog.SetupEncryptionDialogFragment;
import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment;
import com.owncloud.android.ui.dialog.SharePasswordDialogFragment;
import com.owncloud.android.ui.dialog.SortingOrderDialogFragment;
import com.owncloud.android.ui.dialog.SslUntrustedCertDialog;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ object StringConstants {
const val DOT = "."
const val SPACE = " "
}

fun String.getContentOfPublicKey(): String {
return replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replace("\\s+".toRegex(), "")
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
import com.owncloud.android.ui.asynctasks.FetchRemoteFileTask;
import com.owncloud.android.ui.asynctasks.GetRemoteFileTask;
import com.owncloud.android.ui.dialog.SendShareDialog;
import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment;
import com.owncloud.android.ui.dialog.SortingOrderDialogFragment;
import com.owncloud.android.ui.dialog.StoragePermissionDialogFragment;
import com.owncloud.android.ui.events.SearchEvent;
Expand Down Expand Up @@ -157,6 +158,7 @@
import kotlin.Unit;

import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
import static com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment.SETUP_ENCRYPTION_DIALOG_TAG;
import static com.owncloud.android.utils.PermissionUtil.PERMISSION_CHOICE_DIALOG_TAG;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
import com.owncloud.android.providers.DocumentsStorageProvider;
import com.owncloud.android.ui.ThemeableSwitchPreference;
import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask;
import com.owncloud.android.ui.dialog.SetupEncryptionDialogFragment;
import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment;
import com.owncloud.android.ui.helpers.FileOperationsHelper;
import com.owncloud.android.utils.ClipboardUtil;
import com.owncloud.android.utils.DeviceCredentialUtils;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import androidx.appcompat.app.AppCompatActivity
import com.nextcloud.client.account.User
import com.nextcloud.utils.extensions.getParcelableArgument
import com.owncloud.android.R
import com.owncloud.android.ui.dialog.SetupEncryptionDialogFragment
import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment

class SetupEncryptionActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <[email protected]>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.owncloud.android.ui.dialog.setupEncryption

import android.util.Base64
import com.nextcloud.utils.extensions.getContentOfPublicKey
import com.owncloud.android.lib.common.utils.Log_OC
import java.io.ByteArrayInputStream
import java.security.KeyFactory
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.security.spec.X509EncodedKeySpec
import javax.inject.Inject

@Suppress("EmptyClassBlock")
class CertificateValidator @Inject constructor() {
private val tag = "CertificateValidator"

/**
* Validates certificate with given public key
*
* @param serverPublicKeyString Public key with header
* @param certificate Certificate in PEM format
*/
@Suppress("TooGenericExceptionCaught")
fun validate(serverPublicKeyString: String, certificate: String): Boolean {
val contentOfServerKey = serverPublicKeyString.getContentOfPublicKey()

return try {
val decodedPublicKey = Base64.decode(contentOfServerKey, Base64.NO_WRAP)

val keySpec = X509EncodedKeySpec(decodedPublicKey)
val keyFactory = KeyFactory.getInstance("RSA")
val serverPublicKey = keyFactory.generatePublic(keySpec)

val certificateFactory = CertificateFactory.getInstance("X.509")
val certificateInputStream = ByteArrayInputStream(certificate.toByteArray())
val x509Certificate = certificateFactory.generateCertificate(certificateInputStream) as X509Certificate

// Check date of the certificate
x509Certificate.checkValidity()

// Verify certificate with serverPublicKey
x509Certificate.verify(serverPublicKey)
Log_OC.d(tag, "Client certificate is valid against server public key")
true
} catch (e: Exception) {
Log_OC.d(tag, "Client certificate is not valid against the server public key")
false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-FileCopyrightText: 2024 Alper Ozturk <[email protected]>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.ui.dialog
package com.owncloud.android.ui.dialog.setupEncryption

import android.accounts.AccountManager
import android.annotation.SuppressLint
Expand All @@ -22,6 +22,7 @@ import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.client.account.User
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.network.ClientFactory
import com.nextcloud.utils.extensions.getParcelableArgument
import com.owncloud.android.R
import com.owncloud.android.databinding.SetupEncryptionDialogBinding
Expand All @@ -33,6 +34,7 @@ import com.owncloud.android.lib.resources.e2ee.CsrHelper
import com.owncloud.android.lib.resources.users.DeletePublicKeyRemoteOperation
import com.owncloud.android.lib.resources.users.GetPrivateKeyRemoteOperation
import com.owncloud.android.lib.resources.users.GetPublicKeyRemoteOperation
import com.owncloud.android.lib.resources.users.GetServerPublicKeyRemoteOperation
import com.owncloud.android.lib.resources.users.SendCSRRemoteOperation
import com.owncloud.android.lib.resources.users.StorePrivateKeyRemoteOperation
import com.owncloud.android.utils.EncryptionUtils
Expand All @@ -50,6 +52,14 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
@Inject
lateinit var viewThemeUtils: ViewThemeUtils

@JvmField
@Inject
var clientFactory: ClientFactory? = null

@JvmField
@Inject
var certificateValidator: CertificateValidator? = null

private var user: User? = null
private var arbitraryDataProvider: ArbitraryDataProvider? = null
private var positiveButton: MaterialButton? = null
Expand Down Expand Up @@ -270,34 +280,50 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
// if available
// - store public key
// - decrypt private key, store unencrypted private key in database

val context = mWeakContext.get() ?: return null
val publicKeyOperation = GetPublicKeyRemoteOperation()
val certificateOperation = GetPublicKeyRemoteOperation()
val serverPublicKeyOperation = GetServerPublicKeyRemoteOperation()
val user = user ?: return null

val publicKeyResult = publicKeyOperation.executeNextcloudClient(user, context)
val privateKeyOperation = GetPrivateKeyRemoteOperation()
val privateKeyResult = privateKeyOperation.executeNextcloudClient(user, context)
val certificateResult = certificateOperation.executeNextcloudClient(user, context)
val serverPublicKeyResult = serverPublicKeyOperation.executeNextcloudClient(user, context)

if (!publicKeyResult.isSuccess) {
var encryptedPrivateKey: com.owncloud.android.lib.ocs.responses.PrivateKey? = null
if (privateKeyResult.isSuccess) {
encryptedPrivateKey = privateKeyResult.resultData
}

if (!certificateResult.isSuccess || !serverPublicKeyResult.isSuccess) {
Log_OC.d(TAG, "certificate or server public key not fetched")
return null
}

Log_OC.d(TAG, "public key successful downloaded for " + user.accountName)
val serverKey = serverPublicKeyResult.resultData
val certificateAsString = certificateResult.resultData
val isCertificateValid = certificateValidator?.validate(serverKey, certificateAsString)

if (isCertificateValid == false) {
Log_OC.d(TAG, "Could not save certificate, certificate is not valid")
return null
}

if (arbitraryDataProvider == null) {
return null
}

val publicKeyFromServer = publicKeyResult.resultData
arbitraryDataProvider?.storeOrUpdateKeyValue(
user.accountName,
EncryptionUtils.PUBLIC_KEY,
publicKeyFromServer
certificateAsString
)

val privateKeyResult = GetPrivateKeyRemoteOperation().executeNextcloudClient(user, context)
if (privateKeyResult.isSuccess) {
Log_OC.d(TAG, "private key successful downloaded for " + user.accountName)
keyResult = KEY_EXISTING_USED
return privateKeyResult.resultData.getKey()
return encryptedPrivateKey?.getKey()
}

return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment;
import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
import com.owncloud.android.ui.dialog.SetupEncryptionDialogFragment;
import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment;
import com.owncloud.android.ui.dialog.SyncFileNotEnoughSpaceDialogFragment;
import com.owncloud.android.ui.events.ChangeMenuEvent;
import com.owncloud.android.ui.events.CommentsEvent;
Expand Down Expand Up @@ -147,8 +147,8 @@
import androidx.recyclerview.widget.RecyclerView;

import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
import static com.owncloud.android.ui.dialog.SetupEncryptionDialogFragment.SETUP_ENCRYPTION_DIALOG_TAG;
import static com.owncloud.android.ui.dialog.SetupEncryptionDialogFragment.SETUP_ENCRYPTION_REQUEST_CODE;
import static com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment.SETUP_ENCRYPTION_DIALOG_TAG;
import static com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment.SETUP_ENCRYPTION_REQUEST_CODE;
import static com.owncloud.android.ui.fragment.SearchType.FAVORITE_SEARCH;
import static com.owncloud.android.ui.fragment.SearchType.FILE_SEARCH;
import static com.owncloud.android.ui.fragment.SearchType.NO_SEARCH;
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/
buildscript {
ext {
androidLibraryVersion ="86b0279d70"
androidLibraryVersion ="a4d86ef9d1"
androidPluginVersion = '8.5.2'
androidxMediaVersion = '1.4.0'
androidxTestVersion = "1.6.1"
Expand Down
8 changes: 8 additions & 0 deletions gradle/verification-metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6085,6 +6085,14 @@
<sha256 value="6907f3626be02ec7508a98ea912529780445a0336dc4fc665c96af007c5d6c49" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="a4d86ef9d1">
<artifact name="android-library-a4d86ef9d1.aar">
<sha256 value="c6c70775d49d935e7691f43fee0ff51c2c0df03c041fd75da92cf1f0df1385a7" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
<artifact name="android-library-a4d86ef9d1.module">
<sha256 value="47c4737be2cd41173f4e44a12f02e527f0014152103629364d86318c6198d484" origin="Generated by Gradle" reason="Artifact is not signed"/>
</artifact>
</component>
<component group="com.github.nextcloud" name="android-library" version="acc7df66e4a43ed7f450136c13753f2743fb245e">
<artifact name="android-library-acc7df66e4a43ed7f450136c13753f2743fb245e.aar">
<sha256 value="f30c2d22c923c6ff8c605eb4cf713cfaf49eb967611f70dc3d139725b0891483" origin="Generated by Gradle" reason="Artifact is not signed"/>
Expand Down

0 comments on commit af880a4

Please sign in to comment.