1
+ /*
2
+ * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
3
+ * SPDX-License-Identifier: GPL-3.0-only
4
+ */
5
+
6
+ package dev.msfjarvis.aps.crypto
7
+
8
+ import androidx.annotation.VisibleForTesting
9
+ import com.github.michaelbull.result.Result
10
+ import com.github.michaelbull.result.runCatching
11
+ import java.io.File
12
+ import kotlinx.coroutines.CoroutineDispatcher
13
+ import kotlinx.coroutines.withContext
14
+ import org.pgpainless.PGPainless
15
+
16
+ public class PGPKeyManager (filesDir : String , private val dispatcher : CoroutineDispatcher ) : KeyManager<PGPKeyPair> {
17
+
18
+ private val keyDir = File (filesDir, KEY_DIR_NAME )
19
+
20
+ override suspend fun addKey (key : PGPKeyPair , replace : Boolean ): Result <PGPKeyPair , Throwable > =
21
+ withContext(dispatcher) {
22
+ runCatching {
23
+ if (! keyDirExists()) throw KeyManagerException .KeyDirectoryUnavailableException
24
+ val keyFile = File (keyDir, " ${key.getKeyId()} .$KEY_EXTENSION " )
25
+ if (keyFile.exists()) {
26
+ // Check for replace flag first and if it is false, throw an error
27
+ if (! replace) throw KeyManagerException .KeyAlreadyExistsException (key.getKeyId())
28
+ if (! keyFile.delete()) throw KeyManagerException .KeyDeletionFailedException
29
+ }
30
+
31
+ keyFile.writeBytes(key.getPrivateKey())
32
+
33
+ key
34
+ }
35
+ }
36
+
37
+ override suspend fun removeKey (key : PGPKeyPair ): Result <PGPKeyPair , Throwable > =
38
+ withContext(dispatcher) {
39
+ runCatching {
40
+ if (! keyDirExists()) throw KeyManagerException .KeyDirectoryUnavailableException
41
+ val keyFile = File (keyDir, " ${key.getKeyId()} .$KEY_EXTENSION " )
42
+ if (keyFile.exists()) {
43
+ if (! keyFile.delete()) throw KeyManagerException .KeyDeletionFailedException
44
+ }
45
+
46
+ key
47
+ }
48
+ }
49
+
50
+ override suspend fun getKeyById (id : String ): Result <PGPKeyPair , Throwable > =
51
+ withContext(dispatcher) {
52
+ runCatching {
53
+ if (! keyDirExists()) throw KeyManagerException .KeyDirectoryUnavailableException
54
+ val keys = keyDir.listFiles()
55
+ if (keys.isNullOrEmpty()) throw KeyManagerException .NoKeysAvailableException
56
+
57
+ for (keyFile in keys) {
58
+ val secretKeyRing = PGPainless .readKeyRing().secretKeyRing(keyFile.inputStream())
59
+ val secretKey = secretKeyRing.secretKey
60
+ val keyPair = PGPKeyPair (secretKey)
61
+ if (keyPair.getKeyId() == id) return @runCatching keyPair
62
+ }
63
+
64
+ throw KeyManagerException .KeyNotFoundException (id)
65
+ }
66
+ }
67
+
68
+ override suspend fun getAllKeys (): Result <List <PGPKeyPair >, Throwable> =
69
+ withContext(dispatcher) {
70
+ runCatching {
71
+ if (! keyDirExists()) throw KeyManagerException .KeyDirectoryUnavailableException
72
+ val keys = keyDir.listFiles()
73
+ if (keys.isNullOrEmpty()) return @runCatching listOf ()
74
+
75
+ keys.map { keyFile ->
76
+ val secretKeyRing = PGPainless .readKeyRing().secretKeyRing(keyFile.inputStream())
77
+ val secretKey = secretKeyRing.secretKey
78
+
79
+ PGPKeyPair (secretKey)
80
+ }.toList()
81
+ }
82
+ }
83
+
84
+ override fun canHandle (fileName : String ): Boolean {
85
+ // TODO: This is a temp hack for now and in future it should check that the GPGKeyManager can
86
+ // decrypt the file
87
+ return fileName.endsWith(KEY_EXTENSION )
88
+ }
89
+
90
+ private fun keyDirExists (): Boolean {
91
+ return keyDir.exists() || keyDir.mkdirs()
92
+ }
93
+
94
+ internal companion object {
95
+
96
+ @VisibleForTesting(otherwise = VisibleForTesting .PRIVATE )
97
+ internal const val KEY_DIR_NAME : String = " keys"
98
+ @VisibleForTesting(otherwise = VisibleForTesting .PRIVATE )
99
+ internal const val KEY_EXTENSION : String = " key"
100
+ }
101
+ }
0 commit comments