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

Commit 6e4ffe2

Browse files
authored
Add initial implementation of Gopenpgp-backed PGP (#1441)
1 parent 9c388e4 commit 6e4ffe2

File tree

20 files changed

+966
-4
lines changed

20 files changed

+966
-4
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ dependencies {
7676
implementation(libs.androidx.annotation)
7777
coreLibraryDesugaring(libs.android.desugarJdkLibs)
7878
implementation(projects.autofillParser)
79+
implementation(projects.cryptoPgp)
7980
implementation(projects.formatCommon)
8081
implementation(projects.openpgpKtx)
8182
implementation(libs.androidx.activity.ktx)

app/src/main/AndroidManifest.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
android:name=".ui.proxy.ProxySelectorActivity"
4545
android:windowSoftInputMode="adjustResize" />
4646

47+
<activity
48+
android:name=".ui.crypto.GopenpgpDecryptActivity"
49+
android:exported="true" />
50+
4751
<activity
4852
android:name=".ui.main.LaunchActivity"
4953
android:configChanges="orientation|screenSize"
@@ -91,6 +95,10 @@
9195
android:label="@string/new_password_title"
9296
android:windowSoftInputMode="adjustResize" />
9397

98+
<activity android:name=".ui.crypto.GopenpgpPasswordCreationActivity"
99+
android:label="@string/new_password_title"
100+
android:windowSoftInputMode="adjustResize" />
101+
94102
<activity
95103
android:name=".ui.crypto.DecryptActivity"
96104
android:windowSoftInputMode="adjustResize" />
@@ -128,6 +136,9 @@
128136
<activity
129137
android:name=".ui.autofill.AutofillDecryptActivity"
130138
android:theme="@style/NoBackgroundTheme" />
139+
<activity
140+
android:name=".ui.autofill.GopenpgpAutofillDecryptActivity"
141+
android:theme="@style/NoBackgroundTheme" />
131142
<activity
132143
android:name=".ui.autofill.AutofillFilterView"
133144
android:configChanges="orientation|keyboardHidden"
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
3+
* SPDX-License-Identifier: GPL-3.0-only
4+
*/
5+
6+
package dev.msfjarvis.aps.injection.crypto
7+
8+
import dagger.Module
9+
import dagger.Provides
10+
import dagger.hilt.InstallIn
11+
import dagger.hilt.components.SingletonComponent
12+
import dagger.multibindings.IntoSet
13+
import dev.msfjarvis.aps.data.crypto.CryptoHandler
14+
import dev.msfjarvis.aps.data.crypto.GopenpgpCryptoHandler
15+
16+
/**
17+
* This module adds all [CryptoHandler] implementations into a Set which makes it easier to build
18+
* generic UIs which are not tied to a specific implementation because of injection.
19+
*/
20+
@Module
21+
@InstallIn(SingletonComponent::class)
22+
object CryptoHandlerModule {
23+
@Provides
24+
@IntoSet
25+
fun providePgpCryptoHandler(): CryptoHandler {
26+
return GopenpgpCryptoHandler()
27+
}
28+
}
29+
30+
/** Typealias for a [Set] of [CryptoHandler] instances injected by Dagger. */
31+
typealias CryptoSet = Set<@JvmSuppressWildcards CryptoHandler>

app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillFilterActivity.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import com.github.androidpasswordstore.autofillparser.FormOrigin
2929
import dev.msfjarvis.aps.R
3030
import dev.msfjarvis.aps.data.password.PasswordItem
3131
import dev.msfjarvis.aps.databinding.ActivityOreoAutofillFilterBinding
32+
import dev.msfjarvis.aps.util.FeatureFlags
3233
import dev.msfjarvis.aps.util.autofill.AutofillMatcher
3334
import dev.msfjarvis.aps.util.autofill.AutofillPreferences
3435
import dev.msfjarvis.aps.util.autofill.DirectoryStructure
@@ -220,7 +221,11 @@ class AutofillFilterView : AppCompatActivity() {
220221
AutofillMatcher.addMatchFor(applicationContext, formOrigin, item.file)
221222
// intent?.extras? is checked to be non-null in onCreate
222223
decryptAction.launch(
223-
AutofillDecryptActivity.makeDecryptFileIntent(item.file, intent!!.extras!!, this)
224+
if (FeatureFlags.ENABLE_GOPENPGP) {
225+
GopenpgpAutofillDecryptActivity.makeDecryptFileIntent(item.file, intent!!.extras!!, this)
226+
} else {
227+
AutofillDecryptActivity.makeDecryptFileIntent(item.file, intent!!.extras!!, this)
228+
}
224229
)
225230
}
226231
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
3+
* SPDX-License-Identifier: GPL-3.0-only
4+
*/
5+
package dev.msfjarvis.aps.ui.autofill
6+
7+
import android.app.PendingIntent
8+
import android.content.Context
9+
import android.content.Intent
10+
import android.content.IntentSender
11+
import android.os.Build
12+
import android.os.Bundle
13+
import android.view.autofill.AutofillManager
14+
import androidx.annotation.RequiresApi
15+
import androidx.appcompat.app.AppCompatActivity
16+
import androidx.lifecycle.lifecycleScope
17+
import com.github.ajalt.timberkt.d
18+
import com.github.ajalt.timberkt.e
19+
import com.github.androidpasswordstore.autofillparser.AutofillAction
20+
import com.github.androidpasswordstore.autofillparser.Credentials
21+
import com.github.michaelbull.result.getOrElse
22+
import com.github.michaelbull.result.onFailure
23+
import com.github.michaelbull.result.onSuccess
24+
import com.github.michaelbull.result.runCatching
25+
import dagger.hilt.android.AndroidEntryPoint
26+
import dev.msfjarvis.aps.injection.crypto.CryptoSet
27+
import dev.msfjarvis.aps.injection.password.PasswordEntryFactory
28+
import dev.msfjarvis.aps.ui.crypto.GopenpgpDecryptActivity
29+
import dev.msfjarvis.aps.util.autofill.AutofillPreferences
30+
import dev.msfjarvis.aps.util.autofill.AutofillResponseBuilder
31+
import dev.msfjarvis.aps.util.autofill.DirectoryStructure
32+
import java.io.File
33+
import javax.inject.Inject
34+
import kotlinx.coroutines.Dispatchers
35+
import kotlinx.coroutines.launch
36+
import kotlinx.coroutines.withContext
37+
38+
@RequiresApi(Build.VERSION_CODES.O)
39+
@AndroidEntryPoint
40+
class GopenpgpAutofillDecryptActivity : AppCompatActivity() {
41+
42+
companion object {
43+
44+
private const val EXTRA_FILE_PATH = "dev.msfjarvis.aps.autofill.oreo.EXTRA_FILE_PATH"
45+
private const val EXTRA_SEARCH_ACTION = "dev.msfjarvis.aps.autofill.oreo.EXTRA_SEARCH_ACTION"
46+
47+
private var decryptFileRequestCode = 1
48+
49+
fun makeDecryptFileIntent(file: File, forwardedExtras: Bundle, context: Context): Intent {
50+
return Intent(context, GopenpgpAutofillDecryptActivity::class.java).apply {
51+
putExtras(forwardedExtras)
52+
putExtra(EXTRA_SEARCH_ACTION, true)
53+
putExtra(EXTRA_FILE_PATH, file.absolutePath)
54+
}
55+
}
56+
57+
fun makeDecryptFileIntentSender(file: File, context: Context): IntentSender {
58+
val intent =
59+
Intent(context, GopenpgpAutofillDecryptActivity::class.java).apply {
60+
putExtra(EXTRA_SEARCH_ACTION, false)
61+
putExtra(EXTRA_FILE_PATH, file.absolutePath)
62+
}
63+
return PendingIntent.getActivity(
64+
context,
65+
decryptFileRequestCode++,
66+
intent,
67+
PendingIntent.FLAG_CANCEL_CURRENT
68+
)
69+
.intentSender
70+
}
71+
}
72+
73+
@Inject lateinit var passwordEntryFactory: PasswordEntryFactory
74+
@Inject lateinit var cryptos: CryptoSet
75+
76+
private lateinit var directoryStructure: DirectoryStructure
77+
78+
override fun onStart() {
79+
super.onStart()
80+
val filePath =
81+
intent?.getStringExtra(EXTRA_FILE_PATH)
82+
?: run {
83+
e { "GopenpgpAutofillDecryptActivity started without EXTRA_FILE_PATH" }
84+
finish()
85+
return
86+
}
87+
val clientState =
88+
intent?.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE)
89+
?: run {
90+
e { "GopenpgpAutofillDecryptActivity started without EXTRA_CLIENT_STATE" }
91+
finish()
92+
return
93+
}
94+
val isSearchAction = intent?.getBooleanExtra(EXTRA_SEARCH_ACTION, true)!!
95+
val action = if (isSearchAction) AutofillAction.Search else AutofillAction.Match
96+
directoryStructure = AutofillPreferences.directoryStructure(this)
97+
d { action.toString() }
98+
lifecycleScope.launch {
99+
val credentials = decryptCredential(File(filePath))
100+
if (credentials == null) {
101+
setResult(RESULT_CANCELED)
102+
} else {
103+
val fillInDataset =
104+
AutofillResponseBuilder.makeFillInDataset(
105+
this@GopenpgpAutofillDecryptActivity,
106+
credentials,
107+
clientState,
108+
action
109+
)
110+
withContext(Dispatchers.Main) {
111+
setResult(
112+
RESULT_OK,
113+
Intent().apply { putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset) }
114+
)
115+
}
116+
}
117+
withContext(Dispatchers.Main) { finish() }
118+
}
119+
}
120+
121+
private suspend fun decryptCredential(file: File): Credentials? {
122+
runCatching { file.inputStream() }
123+
.onFailure { e ->
124+
e(e) { "File to decrypt not found" }
125+
return null
126+
}
127+
.onSuccess { encryptedInput ->
128+
runCatching {
129+
val crypto = cryptos.first { it.canHandle(file.absolutePath) }
130+
withContext(Dispatchers.IO) {
131+
crypto.decrypt(
132+
GopenpgpDecryptActivity.PRIV_KEY,
133+
GopenpgpDecryptActivity.PASS.toByteArray(charset = Charsets.UTF_8),
134+
encryptedInput.readBytes()
135+
)
136+
}
137+
}
138+
.onFailure { e ->
139+
e(e) { "Decryption with Gopenpgp failed" }
140+
return null
141+
}
142+
.onSuccess { result ->
143+
return runCatching {
144+
val entry = passwordEntryFactory.create(lifecycleScope, result)
145+
AutofillPreferences.credentialsFromStoreEntry(this, file, entry, directoryStructure)
146+
}
147+
.getOrElse { e ->
148+
e(e) { "Failed to parse password entry" }
149+
return null
150+
}
151+
}
152+
}
153+
return null
154+
}
155+
}

0 commit comments

Comments
 (0)