diff options
Diffstat (limited to 'crypto')
3 files changed, 36 insertions, 0 deletions
diff --git a/crypto/common/src/main/kotlin/app/passwordstore/crypto/CryptoHandler.kt b/crypto/common/src/main/kotlin/app/passwordstore/crypto/CryptoHandler.kt index 898cf058..c823342b 100644 --- a/crypto/common/src/main/kotlin/app/passwordstore/crypto/CryptoHandler.kt +++ b/crypto/common/src/main/kotlin/app/passwordstore/crypto/CryptoHandler.kt @@ -41,4 +41,9 @@ public interface CryptoHandler<Key, EncOpts : CryptoOptions, DecryptOpts : Crypt /** Given a [fileName], return whether this instance can handle it. */ public fun canHandle(fileName: String): Boolean + + /** + * Inspects the given [keys] and returns `false` if none of them require a passphrase to decrypt. + */ + public fun isPassphraseProtected(keys: List<Key>): Boolean } diff --git a/crypto/pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandler.kt b/crypto/pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandler.kt index 1aabe54f..b3f2a64b 100644 --- a/crypto/pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandler.kt +++ b/crypto/pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandler.kt @@ -11,6 +11,7 @@ import app.passwordstore.crypto.errors.NoKeysProvidedException import app.passwordstore.crypto.errors.NonStandardAEAD import app.passwordstore.crypto.errors.UnknownError import com.github.michaelbull.result.Result +import com.github.michaelbull.result.mapBoth import com.github.michaelbull.result.mapError import com.github.michaelbull.result.runCatching import java.io.InputStream @@ -140,4 +141,14 @@ public class PGPainlessCryptoHandler @Inject constructor() : public override fun canHandle(fileName: String): Boolean { return fileName.substringAfterLast('.', "") == "gpg" } + + public override fun isPassphraseProtected(keys: List<PGPKey>): Boolean = + keys + .mapNotNull { key -> PGPainless.readKeyRing().secretKeyRing(key.contents) } + .map(::keyringHasPassphrase) + .all { it } + + internal fun keyringHasPassphrase(keyRing: PGPSecretKeyRing) = + runCatching { keyRing.secretKey.extractPrivateKey(null) } + .mapBoth(success = { false }, failure = { true }) } diff --git a/crypto/pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandlerTest.kt b/crypto/pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandlerTest.kt index 5de2bf4f..600cc39d 100644 --- a/crypto/pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandlerTest.kt +++ b/crypto/pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPainlessCryptoHandlerTest.kt @@ -156,6 +156,26 @@ class PGPainlessCryptoHandlerTest { } @Test + fun detectsKeysWithPassphrase() { + assertTrue(cryptoHandler.isPassphraseProtected(listOf(PGPKey(TestUtils.getArmoredSecretKey())))) + assertTrue( + cryptoHandler.isPassphraseProtected( + listOf(PGPKey(TestUtils.getArmoredSecretKeyWithMultipleIdentities())) + ) + ) + } + + @Test + fun detectsKeysWithoutPassphrase() { + // Uses the internal method instead of the public API because GnuPG seems to have made it + // impossible to generate a key without a passphrase and I can't care to find a magical + // incantation to convince it I am smarter than whatever they are protecting against. + assertFalse( + cryptoHandler.keyringHasPassphrase(PGPainless.generateKeyRing().modernKeyRing("John Doe")) + ) + } + + @Test fun canHandleFiltersFormats() { assertFalse { cryptoHandler.canHandle("example.com") } assertTrue { cryptoHandler.canHandle("example.com.gpg") } |