Skip to content
This repository was archived by the owner on Oct 15, 2024. It is now read-only.

Add initial implementation of Gopenpgp-backed PGP #1441

Merged
merged 19 commits into from
Jul 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ dependencies {
implementation(libs.androidx.annotation)
coreLibraryDesugaring(libs.android.desugarJdkLibs)
implementation(projects.autofillParser)
implementation(projects.cryptoPgp)
implementation(projects.formatCommon)
implementation(projects.openpgpKtx)
implementation(libs.androidx.activity.ktx)
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
android:name=".ui.proxy.ProxySelectorActivity"
android:windowSoftInputMode="adjustResize" />

<activity
android:name=".ui.crypto.GopenpgpDecryptActivity"
android:exported="true" />

<activity
android:name=".ui.main.LaunchActivity"
android:configChanges="orientation|screenSize"
Expand Down Expand Up @@ -91,6 +95,10 @@
android:label="@string/new_password_title"
android:windowSoftInputMode="adjustResize" />

<activity android:name=".ui.crypto.GopenpgpPasswordCreationActivity"
android:label="@string/new_password_title"
android:windowSoftInputMode="adjustResize" />

<activity
android:name=".ui.crypto.DecryptActivity"
android:windowSoftInputMode="adjustResize" />
Expand Down Expand Up @@ -128,6 +136,9 @@
<activity
android:name=".ui.autofill.AutofillDecryptActivity"
android:theme="@style/NoBackgroundTheme" />
<activity
android:name=".ui.autofill.GopenpgpAutofillDecryptActivity"
android:theme="@style/NoBackgroundTheme" />
<activity
android:name=".ui.autofill.AutofillFilterView"
android:configChanges="orientation|keyboardHidden"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/

package dev.msfjarvis.aps.injection.crypto

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.IntoSet
import dev.msfjarvis.aps.data.crypto.CryptoHandler
import dev.msfjarvis.aps.data.crypto.GopenpgpCryptoHandler

/**
* This module adds all [CryptoHandler] implementations into a Set which makes it easier to build
* generic UIs which are not tied to a specific implementation because of injection.
*/
@Module
@InstallIn(SingletonComponent::class)
object CryptoHandlerModule {
@Provides
@IntoSet
fun providePgpCryptoHandler(): CryptoHandler {
return GopenpgpCryptoHandler()
}
}

/** Typealias for a [Set] of [CryptoHandler] instances injected by Dagger. */
typealias CryptoSet = Set<@JvmSuppressWildcards CryptoHandler>
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.github.androidpasswordstore.autofillparser.FormOrigin
import dev.msfjarvis.aps.R
import dev.msfjarvis.aps.data.password.PasswordItem
import dev.msfjarvis.aps.databinding.ActivityOreoAutofillFilterBinding
import dev.msfjarvis.aps.util.FeatureFlags
import dev.msfjarvis.aps.util.autofill.AutofillMatcher
import dev.msfjarvis.aps.util.autofill.AutofillPreferences
import dev.msfjarvis.aps.util.autofill.DirectoryStructure
Expand Down Expand Up @@ -220,7 +221,11 @@ class AutofillFilterView : AppCompatActivity() {
AutofillMatcher.addMatchFor(applicationContext, formOrigin, item.file)
// intent?.extras? is checked to be non-null in onCreate
decryptAction.launch(
AutofillDecryptActivity.makeDecryptFileIntent(item.file, intent!!.extras!!, this)
if (FeatureFlags.ENABLE_GOPENPGP) {
GopenpgpAutofillDecryptActivity.makeDecryptFileIntent(item.file, intent!!.extras!!, this)
} else {
AutofillDecryptActivity.makeDecryptFileIntent(item.file, intent!!.extras!!, this)
}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/
package dev.msfjarvis.aps.ui.autofill

import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.IntentSender
import android.os.Build
import android.os.Bundle
import android.view.autofill.AutofillManager
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.github.ajalt.timberkt.d
import com.github.ajalt.timberkt.e
import com.github.androidpasswordstore.autofillparser.AutofillAction
import com.github.androidpasswordstore.autofillparser.Credentials
import com.github.michaelbull.result.getOrElse
import com.github.michaelbull.result.onFailure
import com.github.michaelbull.result.onSuccess
import com.github.michaelbull.result.runCatching
import dagger.hilt.android.AndroidEntryPoint
import dev.msfjarvis.aps.injection.crypto.CryptoSet
import dev.msfjarvis.aps.injection.password.PasswordEntryFactory
import dev.msfjarvis.aps.ui.crypto.GopenpgpDecryptActivity
import dev.msfjarvis.aps.util.autofill.AutofillPreferences
import dev.msfjarvis.aps.util.autofill.AutofillResponseBuilder
import dev.msfjarvis.aps.util.autofill.DirectoryStructure
import java.io.File
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

@RequiresApi(Build.VERSION_CODES.O)
@AndroidEntryPoint
class GopenpgpAutofillDecryptActivity : AppCompatActivity() {

companion object {

private const val EXTRA_FILE_PATH = "dev.msfjarvis.aps.autofill.oreo.EXTRA_FILE_PATH"
private const val EXTRA_SEARCH_ACTION = "dev.msfjarvis.aps.autofill.oreo.EXTRA_SEARCH_ACTION"

private var decryptFileRequestCode = 1

fun makeDecryptFileIntent(file: File, forwardedExtras: Bundle, context: Context): Intent {
return Intent(context, GopenpgpAutofillDecryptActivity::class.java).apply {
putExtras(forwardedExtras)
putExtra(EXTRA_SEARCH_ACTION, true)
putExtra(EXTRA_FILE_PATH, file.absolutePath)
}
}

fun makeDecryptFileIntentSender(file: File, context: Context): IntentSender {
val intent =
Intent(context, GopenpgpAutofillDecryptActivity::class.java).apply {
putExtra(EXTRA_SEARCH_ACTION, false)
putExtra(EXTRA_FILE_PATH, file.absolutePath)
}
return PendingIntent.getActivity(
context,
decryptFileRequestCode++,
intent,
PendingIntent.FLAG_CANCEL_CURRENT
)
.intentSender
}
}

@Inject lateinit var passwordEntryFactory: PasswordEntryFactory
@Inject lateinit var cryptos: CryptoSet

private lateinit var directoryStructure: DirectoryStructure

override fun onStart() {
super.onStart()
val filePath =
intent?.getStringExtra(EXTRA_FILE_PATH)
?: run {
e { "GopenpgpAutofillDecryptActivity started without EXTRA_FILE_PATH" }
finish()
return
}
val clientState =
intent?.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE)
?: run {
e { "GopenpgpAutofillDecryptActivity started without EXTRA_CLIENT_STATE" }
finish()
return
}
val isSearchAction = intent?.getBooleanExtra(EXTRA_SEARCH_ACTION, true)!!
val action = if (isSearchAction) AutofillAction.Search else AutofillAction.Match
directoryStructure = AutofillPreferences.directoryStructure(this)
d { action.toString() }
lifecycleScope.launch {
val credentials = decryptCredential(File(filePath))
if (credentials == null) {
setResult(RESULT_CANCELED)
} else {
val fillInDataset =
AutofillResponseBuilder.makeFillInDataset(
this@GopenpgpAutofillDecryptActivity,
credentials,
clientState,
action
)
withContext(Dispatchers.Main) {
setResult(
RESULT_OK,
Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) }
)
}
}
withContext(Dispatchers.Main) { finish() }
}
}

private suspend fun decryptCredential(file: File): Credentials? {
runCatching { file.inputStream() }
.onFailure { e ->
e(e) { "File to decrypt not found" }
return null
}
.onSuccess { encryptedInput ->
runCatching {
val crypto = cryptos.first { it.canHandle(file.absolutePath) }
withContext(Dispatchers.IO) {
crypto.decrypt(
GopenpgpDecryptActivity.PRIV_KEY,
GopenpgpDecryptActivity.PASS.toByteArray(charset = Charsets.UTF_8),
encryptedInput.readBytes()
)
}
}
.onFailure { e ->
e(e) { "Decryption with Gopenpgp failed" }
return null
}
.onSuccess { result ->
return runCatching {
val entry = passwordEntryFactory.create(lifecycleScope, result)
AutofillPreferences.credentialsFromStoreEntry(this, file, entry, directoryStructure)
}
.getOrElse { e ->
e(e) { "Failed to parse password entry" }
return null
}
}
}
return null
}
}
Loading