aboutsummaryrefslogtreecommitdiff
path: root/crypto-pgp/src/main/kotlin
diff options
context:
space:
mode:
authorAditya Wasan <adityawasan55@gmail.com>2021-08-17 04:14:43 +0530
committerGitHub <noreply@github.com>2021-08-17 04:14:43 +0530
commitb7abd561f561af451ec717746e198a8686d10868 (patch)
tree16af9397d205d6501624ba4e98389e21764d9dd3 /crypto-pgp/src/main/kotlin
parent9982562dc4e1ab4dbd058cf9d3c3c46fc598dec8 (diff)
Add `KeyPair` and `KeyManager` to manage keys in the app (#1487)
Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
Diffstat (limited to 'crypto-pgp/src/main/kotlin')
-rw-r--r--crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GPGKeyManager.kt95
-rw-r--r--crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GPGKeyPair.kt28
2 files changed, 123 insertions, 0 deletions
diff --git a/crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GPGKeyManager.kt b/crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GPGKeyManager.kt
new file mode 100644
index 00000000..478d2700
--- /dev/null
+++ b/crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GPGKeyManager.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.data.crypto
+
+import androidx.annotation.VisibleForTesting
+import com.github.michaelbull.result.Result
+import com.github.michaelbull.result.runCatching
+import com.proton.Gopenpgp.crypto.Crypto
+import java.io.File
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+public class GPGKeyManager(filesDir: String, private val dispatcher: CoroutineDispatcher) :
+ KeyManager<GPGKeyPair> {
+
+ private val keyDir = File(filesDir, KEY_DIR_NAME)
+
+ override suspend fun addKey(key: GPGKeyPair, replace: Boolean): Result<GPGKeyPair, Throwable> =
+ withContext(dispatcher) {
+ runCatching {
+ if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
+ val keyFile = File(keyDir, "${key.getKeyId()}.$KEY_EXTENSION")
+ if (keyFile.exists()) {
+ // Check for replace flag first and if it is false, throw an error
+ if (!replace) throw KeyManagerException.KeyAlreadyExistsException(key.getKeyId())
+ if (!keyFile.delete()) throw KeyManagerException.KeyDeletionFailedException
+ }
+
+ keyFile.writeBytes(key.getPrivateKey())
+
+ key
+ }
+ }
+
+ override suspend fun removeKey(key: GPGKeyPair): Result<GPGKeyPair, Throwable> =
+ withContext(dispatcher) {
+ runCatching {
+ if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
+ val keyFile = File(keyDir, "${key.getKeyId()}.$KEY_EXTENSION")
+ if (keyFile.exists()) {
+ if (!keyFile.delete()) throw KeyManagerException.KeyDeletionFailedException
+ }
+
+ key
+ }
+ }
+
+ override suspend fun getKeyById(id: String): Result<GPGKeyPair, Throwable> =
+ withContext(dispatcher) {
+ runCatching {
+ if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
+ val keys = keyDir.listFiles()
+ if (keys.isNullOrEmpty()) throw KeyManagerException.NoKeysAvailableException
+
+ for (keyFile in keys) {
+ val keyPair = GPGKeyPair(Crypto.newKeyFromArmored(keyFile.readText()))
+ if (keyPair.getKeyId() == id) return@runCatching keyPair
+ }
+
+ throw KeyManagerException.KeyNotFoundException(id)
+ }
+ }
+
+ override suspend fun getAllKeys(): Result<List<GPGKeyPair>, Throwable> =
+ withContext(dispatcher) {
+ runCatching {
+ if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
+ val keys = keyDir.listFiles()
+ if (keys.isNullOrEmpty()) return@runCatching listOf()
+
+ keys.map { GPGKeyPair(Crypto.newKeyFromArmored(it.readText())) }.toList()
+ }
+ }
+
+ override fun canHandle(fileName: String): Boolean {
+ // TODO: This is a temp hack for now and in future it should check that the GPGKeyManager can
+ // decrypt the file
+ return fileName.endsWith(KEY_EXTENSION)
+ }
+
+ private fun keyDirExists(): Boolean {
+ return keyDir.exists() || keyDir.mkdirs()
+ }
+
+ internal companion object {
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal const val KEY_DIR_NAME: String = "keys"
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ internal const val KEY_EXTENSION: String = "key"
+ }
+}
diff --git a/crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GPGKeyPair.kt b/crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GPGKeyPair.kt
new file mode 100644
index 00000000..2dbe8689
--- /dev/null
+++ b/crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GPGKeyPair.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.data.crypto
+
+import com.proton.Gopenpgp.crypto.Key
+
+/** Wraps a Gopenpgp [Key] to implement [KeyPair]. */
+public class GPGKeyPair(private val key: Key) : KeyPair {
+
+ init {
+ if (!key.isPrivate) throw KeyPairException.PrivateKeyUnavailableException
+ }
+
+ override fun getPrivateKey(): ByteArray {
+ return key.armor().encodeToByteArray()
+ }
+
+ override fun getPublicKey(): ByteArray {
+ return key.armoredPublicKey.encodeToByteArray()
+ }
+
+ override fun getKeyId(): String {
+ return key.hexKeyID
+ }
+}