diff options
Diffstat (limited to 'crypto/pgpainless/src')
3 files changed, 17 insertions, 0 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 311e9049..9acd0489 100644 --- a/crypto/pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt +++ b/crypto/pgpainless/src/main/kotlin/app/passwordstore/crypto/KeyUtils.kt @@ -41,6 +41,8 @@ public object KeyUtils { } /** True when [key] parses as a secret key ring, the only kind that can decrypt. */ + // ponytail: GnuPG stub and smartcard keyrings (no usable secret material) count as secret too. + // Upgrade path: check for a secret subkey usable for decryption. public fun isSecretKey(key: PGPKey): Boolean = tryParseKeyring(key) is PGPSecretKeyRing /** diff --git a/crypto/pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKeyManager.kt b/crypto/pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKeyManager.kt index 6f852af4..ef58ad19 100644 --- a/crypto/pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKeyManager.kt +++ b/crypto/pgpainless/src/main/kotlin/app/passwordstore/crypto/PGPKeyManager.kt @@ -14,6 +14,7 @@ import app.passwordstore.crypto.errors.InvalidKeyException import app.passwordstore.crypto.errors.KeyAlreadyExistsException import app.passwordstore.crypto.errors.KeyDeletionFailedException import app.passwordstore.crypto.errors.KeyDirectoryUnavailableException +import app.passwordstore.crypto.errors.KeyDowngradeException import app.passwordstore.crypto.errors.KeyNotFoundException import app.passwordstore.crypto.errors.NoKeysAvailableException import app.passwordstore.crypto.errors.UnusableKeyException @@ -59,6 +60,9 @@ constructor(filesDir: String, private val dispatcher: CoroutineDispatcher) : keyFile.writeBytes(keyBytes) return@runSuspendCatching key } + // Never with replace either: it would delete the only copy of the private key. + existingKeyRing is PGPSecretKeyRing && incomingKeyRing is PGPPublicKeyRing -> + throw KeyDowngradeException(tryGetId(key)?.toString() ?: "Failed to retrieve key ID") } // Check for replace flag first and if it is false, throw an error if (!replace) diff --git a/crypto/pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPKeyManagerTest.kt b/crypto/pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPKeyManagerTest.kt index 0525c28a..81a666f4 100644 --- a/crypto/pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPKeyManagerTest.kt +++ b/crypto/pgpainless/src/test/kotlin/app/passwordstore/crypto/PGPKeyManagerTest.kt @@ -9,6 +9,7 @@ import app.passwordstore.crypto.KeyUtils.tryGetId import app.passwordstore.crypto.PGPIdentifier.KeyId import app.passwordstore.crypto.PGPIdentifier.UserId import app.passwordstore.crypto.errors.KeyAlreadyExistsException +import app.passwordstore.crypto.errors.KeyDowngradeException import app.passwordstore.crypto.errors.KeyNotFoundException import app.passwordstore.crypto.errors.NoKeysAvailableException import app.passwordstore.crypto.errors.UnusableKeyException @@ -177,6 +178,16 @@ class PGPKeyManagerTest { } @Test + fun replaceSecretKeyWithPublicKeyIsRefusedEvenWithReplace() = + runTest(dispatcher) { + keyManager.addKey(secretKey).unwrap() + assertIs<KeyDowngradeException>(keyManager.addKey(publicKey, replace = true).unwrapError()) + assertIs<KeyDowngradeException>(keyManager.addKey(publicKey, replace = false).unwrapError()) + // The private key is still the stored one. + assertContentEquals(secretKey.contents, keyManager.getAllKeys().unwrap().single().contents) + } + + @Test fun replacePublicKeyWithSecretKey() = runTest(dispatcher) { assertTrue(keyManager.addKey(publicKey).isOk) |
