Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
13c5ac4
feat(pgp): add OpenPGP provider backend
pando85 Sep 21, 2026
a33e5f5
feat(pgp): add scoped provider interaction handling
pando85 Sep 21, 2026
499c2fa
feat(pgp): add provider selection and public-key retrieval
pando85 Sep 21, 2026
86492d7
feat(passkeys): delegate PGP decryption to selected provider
pando85 Sep 21, 2026
d935e6e
build: add scoped OpenPGP API repository
pando85 Sep 21, 2026
6b6fa04
build: add OpenPGP API dependency
pando85 Sep 21, 2026
f5654c4
build: depend on OpenPGP API client
pando85 Sep 21, 2026
76d0f24
feat(pgp): persist selected external provider
pando85 Sep 21, 2026
94c1d70
feat(passkeys): select external OpenPGP decryptor
pando85 Sep 21, 2026
0ab075c
chore: stage OpenPGP provider integration finish
pando85 Sep 21, 2026
7d4e839
chore: apply and validate OpenPGP provider integration
pando85 Sep 21, 2026
ac582cf
feat(pgp): integrate external OpenPGP providers
github-actions[bot] Sep 21, 2026
c9274fb
fix(pgp): map provider operation result types
pando85 Sep 21, 2026
95345ed
chore: keep catalog changes scoped to OpenPGP
pando85 Sep 21, 2026
e7d89db
fix: restore lifecycle catalog version reference
pando85 Sep 21, 2026
109e6f5
fix: keep version catalog identical except OpenPGP dependency
pando85 Sep 21, 2026
0959eed
chore: add temporary OpenPGP verification workflow
pando85 Sep 21, 2026
f59b46d
fix(pgp): fail closed around provider interaction
pando85 Sep 21, 2026
263f696
fix(pgp): avoid redundant Unit expression
pando85 Sep 21, 2026
6092736
fix(passkeys): resolve all missing provider recipients
pando85 Sep 21, 2026
df4a465
style(passkeys): apply Spotless formatting
pando85 Sep 21, 2026
eedb4f8
fix(pgp): surface provider bind timeouts as failures
pando85 Sep 21, 2026
9052912
fix(pgp): release interaction slot on launch failure
pando85 Sep 21, 2026
741dfa3
test(pgp): make continuation action type explicit
pando85 Sep 21, 2026
04f7089
chore: remove temporary OpenPGP verification workflow
pando85 Sep 21, 2026
857ab3c
test(pgp): use JUnit-compatible Unit signatures
pando85 Sep 21, 2026
d66a70b
test(pgp): use JUnit-compatible Unit signature
pando85 Sep 21, 2026
9963d00
fix(pgp): avoid non-null assertion in provider import
pando85 Sep 21, 2026
3aa8e64
Fix lint: replace runCatching with try-catch and remove stale Invalid…
Sep 21, 2026
e6dfd52
fix(pgp): fail closed for unavailable providers
pando85 Sep 22, 2026
7746d02
fix(pgp): resolve encryption recipients from provider
pando85 Sep 22, 2026
de0aa06
fix(pgp): preserve result types during recipient resolution
pando85 Sep 22, 2026
308e7db
style(pgp): apply repository Kotlin formatting
pando85 Sep 22, 2026
7b53254
fix(pgp): bound external provider output
pando85 Sep 22, 2026
35f0f25
fix(pgp): refresh recipients without altering local key state
pando85 Sep 22, 2026
34a2968
fix(openpgp): fail closed on oversized provider output
pando85 Sep 23, 2026
0209e75
test(openpgp): cover sticky output overflow state
pando85 Sep 23, 2026
73c5beb
fix(openpgp): report resolved provider recipients correctly
pando85 Sep 23, 2026
2a3830f
fix(openpgp): unbind pending provider connections
pando85 Sep 23, 2026
0dce9c3
fix(openpgp): wipe raw provider key output
pando85 Sep 23, 2026
90162ef
test(openpgp): cover provider certificate boundary
pando85 Sep 23, 2026
7a73830
test(openpgp): fix provider boundary regression tests
pando85 Sep 23, 2026
bfb901d
style(openpgp): apply spotless formatting
pando85 Sep 23, 2026
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 @@ -91,6 +91,7 @@ dependencies {
implementation(libs.thirdparty.kotlinResult)
implementation(libs.thirdparty.logcat)
implementation(libs.thirdparty.modernAndroidPrefs)
implementation(libs.thirdparty.openpgp.api)
implementation(libs.thirdparty.sshj)
implementation(libs.thirdparty.bouncycastle.bcprov)
implementation(libs.thirdparty.bouncycastle.bcutil)
Expand Down
30 changes: 1 addition & 29 deletions app/lint-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,35 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 9.2.0" type="baseline" client="gradle" dependencies="false" name="AGP (9.2.0)" variant="all" version="9.2.0">

<issue
id="InvalidPackage"
message="Invalid package reference in library; not included in Android: `javax.naming.directory`. Referenced from `org.bouncycastle.cert.dane.fetcher.JndiDANEFetcherFactory`.">
<location
file="$GRADLE_USER_HOME/caches/modules-2/files-2.1/org.bouncycastle/bcpkix-jdk18on/1.80/5277dfaaef2e92ce1d802499599a0ca7488f86e6/bcpkix-jdk18on-1.80.jar"/>
</issue>

<issue
id="InvalidPackage"
message="Invalid package reference in library; not included in Android: `javax.naming`. Referenced from `org.bouncycastle.cert.dane.fetcher.JndiDANEFetcherFactory.1`.">
<location
file="$GRADLE_USER_HOME/caches/modules-2/files-2.1/org.bouncycastle/bcpkix-jdk18on/1.80/5277dfaaef2e92ce1d802499599a0ca7488f86e6/bcpkix-jdk18on-1.80.jar"/>
</issue>

<issue
id="InvalidPackage"
message="Invalid package reference in org.eclipse.jgit:org.eclipse.jgit; not included in Android: `java.lang.management`. Referenced from `org.eclipse.jgit.util.Monitoring`.">
<location
file="$GRADLE_USER_HOME/caches/modules-2/files-2.1/org.eclipse.jgit/org.eclipse.jgit/6.2.0.202206071550-r/d59e7ae8528fcc3854acbb307de8443b28a5c945/org.eclipse.jgit-6.2.0.202206071550-r.jar"/>
</issue>

<issue
id="InvalidPackage"
message="Invalid package reference in org.eclipse.jgit:org.eclipse.jgit; not included in Android: `javax.management`. Referenced from `org.eclipse.jgit.util.Monitoring`.">
<location
file="$GRADLE_USER_HOME/caches/modules-2/files-2.1/org.eclipse.jgit/org.eclipse.jgit/6.2.0.202206071550-r/d59e7ae8528fcc3854acbb307de8443b28a5c945/org.eclipse.jgit-6.2.0.202206071550-r.jar"/>
</issue>

<issue
<issue
id="MissingQuantity"
message="For locale &quot;it&quot; (Italian) the following quantity should also be defined: `many`"
errorLine1=" &lt;plurals name=&quot;delete_title&quot;>"
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">

<queries>
<intent>
<action android:name="org.openintents.openpgp.IOpenPgpService2" />
</intent>
</queries>

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Expand Down
39 changes: 28 additions & 11 deletions app/src/main/java/app/passwordstore/data/crypto/CryptoRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ constructor(
private val pgpKeyManager: PGPKeyManager,
private val pgpCryptoHandler: PGPainlessCryptoHandler,
private val dispatcherProvider: DispatcherProvider,
private val openPgpProviderRepository: OpenPgpProviderRepository,
@SettingsPreferences private val settings: SharedPreferences,
) {

Expand Down Expand Up @@ -90,7 +91,12 @@ constructor(
}

fun getEmailFromKeyId(identifier: PGPIdentifier): String? {
val key = pgpKeyManager.getKeyById(identifier).get() ?: return null
val key =
if (openPgpProviderRepository.hasSelectedProvider()) {
openPgpProviderRepository.resolvedPublicKeysFor(listOf(identifier))?.firstOrNull()
} else {
pgpKeyManager.getKeyById(identifier).get()
} ?: return null
val userId = KeyUtils.tryGetUserId(key) ?: return null
return PGPIdentifier.splitUserId(userId.email)
}
Expand All @@ -116,8 +122,7 @@ constructor(
encryptedMessage: ByteArrayInputStream,
message: ByteArrayOutputStream,
) = run {
if (passphrases.keys.first() == "") { // New passphrase from user input
// Test it against the PGP identities of current entry
if (passphrases.keys.first() == "") {
identities.mapUntil({ it.second.isOk }) { id ->
encryptedMessage.reset()
message.reset()
Expand All @@ -134,7 +139,7 @@ constructor(
result.getError()?.let { logcat { it.asLog() } }
Pair(id.toString(), result.map { message })
}
} else { // Get the first working cached passphrase
} else {
passphrases.keys.toList().mapUntil({ it.second.isOk }) { id ->
encryptedMessage.reset()
message.reset()
Expand Down Expand Up @@ -172,13 +177,25 @@ constructor(
message: ByteArrayInputStream,
encryptedMessage: ByteArrayOutputStream,
) = run {
// get primary key IDs in order to identify and avoid duplicate keys
val primaryKeyIds =
identities
.mapNotNull { getLongKeyIdFromKeyId(it) }
.distinct()
.mapNotNull { PGPIdentifier.fromString(it) }
val keys = primaryKeyIds.map { id -> pgpKeyManager.getKeyById(id) }.filterOk()
val keys =
if (openPgpProviderRepository.hasSelectedProvider()) {
openPgpProviderRepository.resolvedPublicKeysFor(identities).orEmpty()
} else {
val primaryKeyIds =
identities
.mapNotNull { getLongKeyIdFromKeyId(it) }
.distinct()
.mapNotNull { PGPIdentifier.fromString(it) }
primaryKeyIds.map { id -> pgpKeyManager.getKeyById(id) }.filterOk()
}
encryptWithKeys(keys, message, encryptedMessage)
}

private fun encryptWithKeys(
keys: List<PGPKey>,
message: ByteArrayInputStream,
encryptedMessage: ByteArrayOutputStream,
) = run {
val encryptionOptions =
PGPEncryptOptions.Builder()
.withAsciiArmor(settings.getBoolean(PreferenceKeys.ASCII_ARMOR, false))
Expand Down
Loading
Loading