mirror of
https://github.com/android-password-store/Android-Password-Store
synced 2025-09-03 07:45:08 +00:00
crypto-pgpainless: allow updating existing keys automatically for specific cases
This commit is contained in:
@@ -21,6 +21,9 @@ import java.io.File
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||||
|
import org.bouncycastle.openpgp.PGPSecretKeyRing
|
||||||
|
import org.pgpainless.PGPainless
|
||||||
import org.pgpainless.util.selection.userid.SelectUserId
|
import org.pgpainless.util.selection.userid.SelectUserId
|
||||||
|
|
||||||
public class PGPKeyManager
|
public class PGPKeyManager
|
||||||
@@ -36,9 +39,24 @@ constructor(
|
|||||||
withContext(dispatcher) {
|
withContext(dispatcher) {
|
||||||
runSuspendCatching {
|
runSuspendCatching {
|
||||||
if (!keyDirExists()) throw KeyDirectoryUnavailableException
|
if (!keyDirExists()) throw KeyDirectoryUnavailableException
|
||||||
if (tryParseKeyring(key) == null) throw InvalidKeyException
|
val incomingKeyRing = tryParseKeyring(key) ?: throw InvalidKeyException
|
||||||
val keyFile = File(keyDir, "${tryGetId(key)}.$KEY_EXTENSION")
|
val keyFile = File(keyDir, "${tryGetId(key)}.$KEY_EXTENSION")
|
||||||
if (keyFile.exists()) {
|
if (keyFile.exists()) {
|
||||||
|
val existingKeyBytes = keyFile.readBytes()
|
||||||
|
val existingKeyRing =
|
||||||
|
tryParseKeyring(PGPKey(existingKeyBytes)) ?: throw InvalidKeyException
|
||||||
|
when {
|
||||||
|
existingKeyRing is PGPPublicKeyRing && incomingKeyRing is PGPSecretKeyRing -> {
|
||||||
|
keyFile.writeBytes(key.contents)
|
||||||
|
return@runSuspendCatching key
|
||||||
|
}
|
||||||
|
existingKeyRing is PGPPublicKeyRing && incomingKeyRing is PGPPublicKeyRing -> {
|
||||||
|
val updatedPublicKey = PGPainless.mergeCertificate(existingKeyRing, incomingKeyRing)
|
||||||
|
val keyBytes = PGPainless.asciiArmor(updatedPublicKey).encodeToByteArray()
|
||||||
|
keyFile.writeBytes(keyBytes)
|
||||||
|
return@runSuspendCatching key
|
||||||
|
}
|
||||||
|
}
|
||||||
// Check for replace flag first and if it is false, throw an error
|
// Check for replace flag first and if it is false, throw an error
|
||||||
if (!replace)
|
if (!replace)
|
||||||
throw KeyAlreadyExistsException(
|
throw KeyAlreadyExistsException(
|
||||||
|
@@ -6,6 +6,8 @@ import app.passwordstore.crypto.TestUtils.getArmoredPrivateKeyWithMultipleIdenti
|
|||||||
import app.passwordstore.crypto.errors.KeyAlreadyExistsException
|
import app.passwordstore.crypto.errors.KeyAlreadyExistsException
|
||||||
import app.passwordstore.crypto.errors.KeyNotFoundException
|
import app.passwordstore.crypto.errors.KeyNotFoundException
|
||||||
import app.passwordstore.crypto.errors.NoKeysAvailableException
|
import app.passwordstore.crypto.errors.NoKeysAvailableException
|
||||||
|
import com.github.michaelbull.result.Err
|
||||||
|
import com.github.michaelbull.result.Ok
|
||||||
import com.github.michaelbull.result.unwrap
|
import com.github.michaelbull.result.unwrap
|
||||||
import com.github.michaelbull.result.unwrapError
|
import com.github.michaelbull.result.unwrapError
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -35,7 +37,8 @@ class PGPKeyManagerTest {
|
|||||||
private val dispatcher = StandardTestDispatcher()
|
private val dispatcher = StandardTestDispatcher()
|
||||||
private val scope = TestScope(dispatcher)
|
private val scope = TestScope(dispatcher)
|
||||||
private val keyManager by unsafeLazy { PGPKeyManager(filesDir.absolutePath, dispatcher) }
|
private val keyManager by unsafeLazy { PGPKeyManager(filesDir.absolutePath, dispatcher) }
|
||||||
private val key = PGPKey(TestUtils.getArmoredPrivateKey())
|
private val privateKey = PGPKey(TestUtils.getArmoredPrivateKey())
|
||||||
|
private val publicKey = PGPKey(TestUtils.getArmoredPublicKey())
|
||||||
|
|
||||||
private fun <T> unsafeLazy(initializer: () -> T) =
|
private fun <T> unsafeLazy(initializer: () -> T) =
|
||||||
lazy(LazyThreadSafetyMode.NONE) { initializer.invoke() }
|
lazy(LazyThreadSafetyMode.NONE) { initializer.invoke() }
|
||||||
@@ -54,7 +57,7 @@ class PGPKeyManagerTest {
|
|||||||
fun addKey() =
|
fun addKey() =
|
||||||
scope.runTest {
|
scope.runTest {
|
||||||
// Check if the key id returned is correct
|
// Check if the key id returned is correct
|
||||||
val keyId = keyManager.getKeyId(keyManager.addKey(key).unwrap())
|
val keyId = keyManager.getKeyId(keyManager.addKey(privateKey).unwrap())
|
||||||
assertEquals(KeyId(CryptoConstants.KEY_ID), keyId)
|
assertEquals(KeyId(CryptoConstants.KEY_ID), keyId)
|
||||||
|
|
||||||
// Check if the keys directory have one file
|
// Check if the keys directory have one file
|
||||||
@@ -69,8 +72,8 @@ class PGPKeyManagerTest {
|
|||||||
fun addKeyWithoutReplaceFlag() =
|
fun addKeyWithoutReplaceFlag() =
|
||||||
scope.runTest {
|
scope.runTest {
|
||||||
// Check adding the keys twice
|
// Check adding the keys twice
|
||||||
keyManager.addKey(key, false).unwrap()
|
keyManager.addKey(privateKey, false).unwrap()
|
||||||
val error = keyManager.addKey(key, false).unwrapError()
|
val error = keyManager.addKey(privateKey, false).unwrapError()
|
||||||
|
|
||||||
assertIs<KeyAlreadyExistsException>(error)
|
assertIs<KeyAlreadyExistsException>(error)
|
||||||
}
|
}
|
||||||
@@ -79,8 +82,8 @@ class PGPKeyManagerTest {
|
|||||||
fun addKeyWithReplaceFlag() =
|
fun addKeyWithReplaceFlag() =
|
||||||
scope.runTest {
|
scope.runTest {
|
||||||
// Check adding the keys twice
|
// Check adding the keys twice
|
||||||
keyManager.addKey(key, true).unwrap()
|
keyManager.addKey(privateKey, true).unwrap()
|
||||||
val keyId = keyManager.getKeyId(keyManager.addKey(key, true).unwrap())
|
val keyId = keyManager.getKeyId(keyManager.addKey(privateKey, true).unwrap())
|
||||||
|
|
||||||
assertEquals(KeyId(CryptoConstants.KEY_ID), keyId)
|
assertEquals(KeyId(CryptoConstants.KEY_ID), keyId)
|
||||||
}
|
}
|
||||||
@@ -89,10 +92,10 @@ class PGPKeyManagerTest {
|
|||||||
fun removeKey() =
|
fun removeKey() =
|
||||||
scope.runTest {
|
scope.runTest {
|
||||||
// Add key using KeyManager
|
// Add key using KeyManager
|
||||||
keyManager.addKey(key).unwrap()
|
keyManager.addKey(privateKey).unwrap()
|
||||||
|
|
||||||
// Check if the key id returned is correct
|
// Check if the key id returned is correct
|
||||||
val keyId = keyManager.getKeyId(keyManager.removeKey(key).unwrap())
|
val keyId = keyManager.getKeyId(keyManager.removeKey(privateKey).unwrap())
|
||||||
assertEquals(KeyId(CryptoConstants.KEY_ID), keyId)
|
assertEquals(KeyId(CryptoConstants.KEY_ID), keyId)
|
||||||
|
|
||||||
// Check if the keys directory have 0 files
|
// Check if the keys directory have 0 files
|
||||||
@@ -104,42 +107,42 @@ class PGPKeyManagerTest {
|
|||||||
fun getKeyById() =
|
fun getKeyById() =
|
||||||
scope.runTest {
|
scope.runTest {
|
||||||
// Add key using KeyManager
|
// Add key using KeyManager
|
||||||
keyManager.addKey(key).unwrap()
|
keyManager.addKey(privateKey).unwrap()
|
||||||
|
|
||||||
val keyId = keyManager.getKeyId(key)
|
val keyId = keyManager.getKeyId(privateKey)
|
||||||
assertNotNull(keyId)
|
assertNotNull(keyId)
|
||||||
assertEquals(KeyId(CryptoConstants.KEY_ID), keyManager.getKeyId(key))
|
assertEquals(KeyId(CryptoConstants.KEY_ID), keyManager.getKeyId(privateKey))
|
||||||
|
|
||||||
// Check returned key id matches the expected id and the created key id
|
// Check returned key id matches the expected id and the created key id
|
||||||
val returnedKey = keyManager.getKeyById(keyId).unwrap()
|
val returnedKey = keyManager.getKeyById(keyId).unwrap()
|
||||||
assertEquals(keyManager.getKeyId(key), keyManager.getKeyId(returnedKey))
|
assertEquals(keyManager.getKeyId(privateKey), keyManager.getKeyId(returnedKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getKeyByFullUserId() =
|
fun getKeyByFullUserId() =
|
||||||
scope.runTest {
|
scope.runTest {
|
||||||
keyManager.addKey(key).unwrap()
|
keyManager.addKey(privateKey).unwrap()
|
||||||
|
|
||||||
val keyId = "${CryptoConstants.KEY_NAME} <${CryptoConstants.KEY_EMAIL}>"
|
val keyId = "${CryptoConstants.KEY_NAME} <${CryptoConstants.KEY_EMAIL}>"
|
||||||
val returnedKey = keyManager.getKeyById(UserId(keyId)).unwrap()
|
val returnedKey = keyManager.getKeyById(UserId(keyId)).unwrap()
|
||||||
assertEquals(keyManager.getKeyId(key), keyManager.getKeyId(returnedKey))
|
assertEquals(keyManager.getKeyId(privateKey), keyManager.getKeyId(returnedKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getKeyByEmailUserId() =
|
fun getKeyByEmailUserId() =
|
||||||
scope.runTest {
|
scope.runTest {
|
||||||
keyManager.addKey(key).unwrap()
|
keyManager.addKey(privateKey).unwrap()
|
||||||
|
|
||||||
val keyId = CryptoConstants.KEY_EMAIL
|
val keyId = CryptoConstants.KEY_EMAIL
|
||||||
val returnedKey = keyManager.getKeyById(UserId(keyId)).unwrap()
|
val returnedKey = keyManager.getKeyById(UserId(keyId)).unwrap()
|
||||||
assertEquals(keyManager.getKeyId(key), keyManager.getKeyId(returnedKey))
|
assertEquals(keyManager.getKeyId(privateKey), keyManager.getKeyId(returnedKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getNonExistentKey() =
|
fun getNonExistentKey() =
|
||||||
scope.runTest {
|
scope.runTest {
|
||||||
// Add key using KeyManager
|
// Add key using KeyManager
|
||||||
keyManager.addKey(key).unwrap()
|
keyManager.addKey(privateKey).unwrap()
|
||||||
|
|
||||||
val keyId = KeyId(0x08edf7567183ce44)
|
val keyId = KeyId(0x08edf7567183ce44)
|
||||||
|
|
||||||
@@ -166,7 +169,7 @@ class PGPKeyManagerTest {
|
|||||||
assertEquals(0, noKeyList.size)
|
assertEquals(0, noKeyList.size)
|
||||||
|
|
||||||
// Add key using KeyManager
|
// Add key using KeyManager
|
||||||
keyManager.addKey(key).unwrap()
|
keyManager.addKey(privateKey).unwrap()
|
||||||
keyManager.addKey(PGPKey(getArmoredPrivateKeyWithMultipleIdentities())).unwrap()
|
keyManager.addKey(PGPKey(getArmoredPrivateKeyWithMultipleIdentities())).unwrap()
|
||||||
|
|
||||||
// Check if KeyManager returns one key
|
// Check if KeyManager returns one key
|
||||||
@@ -186,4 +189,41 @@ class PGPKeyManagerTest {
|
|||||||
assertContentEquals(johnKey.contents, janeKey.contents)
|
assertContentEquals(johnKey.contents, janeKey.contents)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun replacePrivateKeyWithPublicKey() {
|
||||||
|
scope.runTest {
|
||||||
|
assertIs<Ok<PGPKey>>(keyManager.addKey(privateKey))
|
||||||
|
assertIs<Err<KeyAlreadyExistsException>>(keyManager.addKey(publicKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun replacePublicKeyWithSecretKey() {
|
||||||
|
scope.runTest {
|
||||||
|
assertIs<Ok<PGPKey>>(keyManager.addKey(publicKey))
|
||||||
|
assertIs<Ok<PGPKey>>(keyManager.addKey(privateKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun replacePublicKeyWithPublicKey() {
|
||||||
|
scope.runTest {
|
||||||
|
assertIs<Ok<PGPKey>>(keyManager.addKey(publicKey))
|
||||||
|
assertIs<Ok<PGPKey>>(keyManager.addKey(publicKey))
|
||||||
|
val allKeys = keyManager.getAllKeys()
|
||||||
|
assertIs<Ok<List<PGPKey>>>(allKeys)
|
||||||
|
assertEquals(1, allKeys.value.size)
|
||||||
|
val key = allKeys.value[0]
|
||||||
|
assertContentEquals(publicKey.contents, key.contents)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun replaceSecretKeyWithSecretKey() {
|
||||||
|
scope.runTest {
|
||||||
|
assertIs<Ok<PGPKey>>(keyManager.addKey(privateKey))
|
||||||
|
assertIs<Err<KeyAlreadyExistsException>>(keyManager.addKey(privateKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user