aboutsummaryrefslogtreecommitdiffstats
path: root/crypto
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-11-03 13:44:42 +0100
committerDanilo M. <danix@danix.xyz>2026-11-03 13:44:42 +0100
commit6a614fe7dc1cbbec5783828d58b246a362edea07 (patch)
treea949264fdde608b789a215f63318af7bae29da96 /crypto
parent9d4b14306444f42ba8a5f11711f0f7381fd7db0f (diff)
downloadHardenedPass-6a614fe7dc1cbbec5783828d58b246a362edea07.tar.gz
HardenedPass-6a614fe7dc1cbbec5783828d58b246a362edea07.zip
feat(app): add the setup model and stricter key checks
URL checks and fixes, authentication modes per URL, the clone target, the store's recipients, a size- and symlink-checked .gpg-id read and the .gpg-id text for a new store (uppercase long key IDs), as pure functions for the Compose setup screens. The SSH key import now also requires a format sshj recognizes (an OpenPGP private key block matched the old marker check and was imported as the SSH key) and no longer crashes on an empty size cursor. KeyUtils tells secret keys apart and no longer throws on a key without user ID. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'crypto')
-rw-r--r--crypto/pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt7
-rw-r--r--crypto/pgpainless/src/test/kotlin/app/passwordstore/crypto/KeyUtilsTest.kt19
2 files changed, 25 insertions, 1 deletions
diff --git a/crypto/pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt b/crypto/pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt
index f85fe92c..311e9049 100644
--- a/crypto/pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt
+++ b/crypto/pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt
@@ -10,6 +10,7 @@ import app.passwordstore.crypto.PGPIdentifier.UserId
import com.github.michaelbull.result.get
import com.github.michaelbull.result.runCatching
import org.bouncycastle.openpgp.PGPKeyRing
+import org.bouncycastle.openpgp.PGPSecretKeyRing
import org.pgpainless.PGPainless
import org.pgpainless.key.parsing.KeyRingReader
@@ -35,9 +36,13 @@ public object KeyUtils {
*/
public fun tryGetEmail(key: PGPKey): UserId? {
val keyRing = tryParseKeyring(key) ?: return null
- return UserId(keyRing.publicKey.userIDs.next())
+ // A key may carry no user ID at all: next() threw NoSuchElementException there.
+ return keyRing.publicKey.userIDs.asSequence().firstOrNull()?.let(::UserId)
}
+ /** True when [key] parses as a secret key ring, the only kind that can decrypt. */
+ public fun isSecretKey(key: PGPKey): Boolean = tryParseKeyring(key) is PGPSecretKeyRing
+
/**
* Tests if the given [key] can be used for encryption, which is a bare minimum necessity for the
* app.
diff --git a/crypto/pgpainless/src/test/kotlin/app/passwordstore/crypto/KeyUtilsTest.kt b/crypto/pgpainless/src/test/kotlin/app/passwordstore/crypto/KeyUtilsTest.kt
index e6e3d7d1..3281c334 100644
--- a/crypto/pgpainless/src/test/kotlin/app/passwordstore/crypto/KeyUtilsTest.kt
+++ b/crypto/pgpainless/src/test/kotlin/app/passwordstore/crypto/KeyUtilsTest.kt
@@ -6,14 +6,18 @@
package app.passwordstore.crypto
import app.passwordstore.crypto.KeyUtils.isKeyUsable
+import app.passwordstore.crypto.KeyUtils.isSecretKey
+import app.passwordstore.crypto.KeyUtils.tryGetEmail
import app.passwordstore.crypto.KeyUtils.tryGetId
import app.passwordstore.crypto.KeyUtils.tryParseKeyring
import app.passwordstore.crypto.TestUtils.AllKeys
import app.passwordstore.crypto.TestUtils.getArmoredSecretKeyWithMultipleIdentities
import kotlin.test.Test
import kotlin.test.assertEquals
+import kotlin.test.assertFalse
import kotlin.test.assertIs
import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
import org.bouncycastle.openpgp.PGPSecretKeyRing
class KeyUtilsTest {
@@ -37,4 +41,19 @@ class KeyUtilsTest {
assertEquals(isUsable, isKeyUsable(key), "${allKeys.name} failed expectation:")
}
}
+
+ @Test
+ fun secretKeysAreTold() {
+ assertTrue(isSecretKey(PGPKey(AllKeys.ARMORED_SEC.keyMaterial)))
+ assertTrue(isSecretKey(PGPKey(AllKeys.GNUPG_OCB_SEC.keyMaterial)))
+ assertFalse(isSecretKey(PGPKey(AllKeys.ARMORED_PUB.keyMaterial)))
+ assertFalse(isSecretKey(PGPKey("not a key".encodeToByteArray())))
+ }
+
+ @Test
+ fun firstUserIdIsReturned() {
+ val key = PGPKey(getArmoredSecretKeyWithMultipleIdentities())
+ assertNotNull(tryGetEmail(key))
+ assertEquals(null, tryGetEmail(PGPKey("not a key".encodeToByteArray())))
+ }
}