Skip to content

Commit

Permalink
wip(crypto-pgpainless): add PGPKeyPair and PGPKeyManager
Browse files Browse the repository at this point in the history
Signed-off-by: Aditya Wasan <adityawasan55@gmail.com>
(cherry picked from commit 02d07e9)
  • Loading branch information
Skrilltrax authored and msfjarvis committed Sep 14, 2021
1 parent 5cf2260 commit 7657259
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 0 deletions.
18 changes: 18 additions & 0 deletions crypto-pgpainless/api/crypto-pgpainless.api
@@ -1,3 +1,21 @@
public final class dev/msfjarvis/aps/crypto/PGPKeyManager : dev/msfjarvis/aps/crypto/KeyManager {
public fun <init> (Ljava/lang/String;Lkotlinx/coroutines/CoroutineDispatcher;)V
public synthetic fun addKey (Ldev/msfjarvis/aps/crypto/KeyPair;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun addKey (Ldev/msfjarvis/aps/crypto/PGPKeyPair;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun canHandle (Ljava/lang/String;)Z
public fun getAllKeys (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun getKeyById (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public synthetic fun removeKey (Ldev/msfjarvis/aps/crypto/KeyPair;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun removeKey (Ldev/msfjarvis/aps/crypto/PGPKeyPair;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class dev/msfjarvis/aps/crypto/PGPKeyPair : dev/msfjarvis/aps/crypto/KeyPair {
public fun <init> (Lorg/bouncycastle/openpgp/PGPSecretKey;)V
public fun getKeyId ()Ljava/lang/String;
public fun getPrivateKey ()[B
public fun getPublicKey ()[B
}

public final class dev/msfjarvis/aps/crypto/PGPainlessCryptoHandler : dev/msfjarvis/aps/crypto/CryptoHandler {
public fun <init> ()V
public fun canHandle (Ljava/lang/String;)Z
Expand Down
3 changes: 3 additions & 0 deletions crypto-pgpainless/build.gradle.kts
Expand Up @@ -10,8 +10,11 @@ plugins {

dependencies {
api(projects.cryptoCommon)
implementation(libs.androidx.annotation)
implementation(libs.dagger.hilt.core)
implementation(libs.kotlin.coroutines.core)
implementation(libs.thirdparty.kotlinResult)
implementation(libs.thirdparty.pgpainless)
testImplementation(libs.bundles.testDependencies)
testImplementation(libs.kotlin.coroutines.test)
}
@@ -0,0 +1,106 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/

package dev.msfjarvis.aps.crypto

import androidx.annotation.VisibleForTesting
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.runCatching
import java.io.File
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import org.pgpainless.PGPainless

public class PGPKeyManager(
filesDir: String,
private val dispatcher: CoroutineDispatcher,
) : KeyManager<PGPKeyPair> {

private val keyDir = File(filesDir, KEY_DIR_NAME)

override suspend fun addKey(key: PGPKeyPair, replace: Boolean): Result<PGPKeyPair, 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: PGPKeyPair): Result<PGPKeyPair, 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<PGPKeyPair, Throwable> =
withContext(dispatcher) {
runCatching {
if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
val keys = keyDir.listFiles()
if (keys.isNullOrEmpty()) throw KeyManagerException.NoKeysAvailableException

for (keyFile in keys) {
val secretKeyRing = PGPainless.readKeyRing().secretKeyRing(keyFile.inputStream())
val secretKey = secretKeyRing.secretKey
val keyPair = PGPKeyPair(secretKey)
if (keyPair.getKeyId() == id) return@runCatching keyPair
}

throw KeyManagerException.KeyNotFoundException(id)
}
}

override suspend fun getAllKeys(): Result<List<PGPKeyPair>, Throwable> =
withContext(dispatcher) {
runCatching {
if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
val keys = keyDir.listFiles()
if (keys.isNullOrEmpty()) return@runCatching listOf()

keys
.map { keyFile ->
val secretKeyRing = PGPainless.readKeyRing().secretKeyRing(keyFile.inputStream())
val secretKey = secretKeyRing.secretKey

PGPKeyPair(secretKey)
}
.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"
}
}
@@ -0,0 +1,25 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/

package dev.msfjarvis.aps.crypto

import org.bouncycastle.openpgp.PGPSecretKey

public class PGPKeyPair(private val secretKey: PGPSecretKey) : KeyPair {

init {
if (secretKey.isPrivateKeyEmpty) throw KeyPairException.PrivateKeyUnavailableException
}

override fun getPrivateKey(): ByteArray {
return secretKey.encoded
}
override fun getPublicKey(): ByteArray {
return secretKey.publicKey.encoded
}
override fun getKeyId(): String {
return secretKey.keyID.toString()
}
}
@@ -0,0 +1,14 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/

package dev.msfjarvis.aps.crypto.utils

internal object CryptoConstants {
internal const val KEY_PASSPHRASE = "hunter2"
internal const val PLAIN_TEXT = "encryption worthy content"
internal const val KEY_NAME = "John Doe"
internal const val KEY_EMAIL = "john.doe@example.com"
internal const val KEY_ID = "04ace699d5b15b7e"
}
@@ -0,0 +1,44 @@
/*
* Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
* SPDX-License-Identifier: GPL-3.0-only
*/

package dev.msfjarvis.aps.crypto

import dev.msfjarvis.aps.crypto.utils.CryptoConstants
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import org.junit.Test
import org.pgpainless.PGPainless

public class GPGKeyPairTest {

@Test
public fun testIfKeyIdIsCorrect() {
val secretKey = PGPainless.readKeyRing().secretKeyRing(getKey()).secretKey
val keyPair = PGPKeyPair(secretKey)

assertEquals(CryptoConstants.KEY_ID, keyPair.getKeyId())
}

@Test
public fun testBuildingKeyPairWithoutPrivateKey() {
assertFailsWith<KeyPairException.PrivateKeyUnavailableException> {
// Get public key object from private key
val publicKey = PGPainless.readKeyRing().secretKeyRing(getKey()).publicKey

// Create secret key ring from public key
val secretKeyRing = PGPainless.readKeyRing().secretKeyRing(publicKey.encoded)

// Get secret key from key ring
val publicSecretKey = secretKeyRing.secretKey

// Try creating a KeyPair from public key
val keyPair = PGPKeyPair(publicSecretKey)

keyPair.getPrivateKey()
}
}

private fun getKey(): String = this::class.java.classLoader.getResource("private_key").readText()
}
18 changes: 18 additions & 0 deletions crypto-pgpainless/src/test/resources/private_key
@@ -0,0 +1,18 @@
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: GopenPGP 2.1.9
Comment: https://gopenpgp.org

xYYEYN+EThYJKwYBBAHaRw8BAQdAh0d9GdVyJV6KbFynPz3sHkdi5RDnKYs+l0x0
rEOEthX+CQMIfg7BTvTTe7pgvNERA1vLXRjSxXyi7tfSV13JRnrapp7YtNUSHLVS
PqbaLBd6+EXx7dJ9mUSUSWVga5mdtLZ/k6e+6dsygeHiJuwxfGbHnc0fSm9obiBE
b2UgPGpvaG4uZG9lQGV4YW1wbGUuY29tPsKIBBMWCAA6BQJg34ROCRAErOaZ1bFb
fhYhBJQ0DPsSHC5XfslyQwSs5pnVsVt+AhsDAh4BAhkBAwsJBwIVCAIiAQAAtgwB
AOa3rnipQPsxgxvOP1V+2kD6ssiwt6BZRWwPcyfeX1h4AP9ozBYr+PSmNbam9bnq
wgXwuQhPJeWTSgILMaiasugGCMeLBGDfhE4SCisGAQQBl1UBBQEBB0ClFQJX/L2G
EX9ucC5mvwj3X/7aDXDFAmIpQeWYSS1negMBCgn+CQMIF1uko+Ym3thgoDWUgM5e
MNmDG3rYkTa7h6mlhhrsYtE/GN78EJHP1ygFzOczU/YdbxSRTZCu697uPCZLWURV
1+b66KLTMNHNaAkoFb2JC8J4BBgWCAAqBQJg34ROCRAErOaZ1bFbfhYhBJQ0DPsS
HC5XfslyQwSs5pnVsVt+AhsMAAB1CgEApNcEivCSp0f8CnV4UCoSRRRekIbP1Ub2
GJx6iRJR8xwA/jicDxdnl/Umfd3mWjGk04R47whiDOXdwjBmC1KVBaMH
=Sfsa
-----END PGP PRIVATE KEY BLOCK-----

0 comments on commit 7657259

Please sign in to comment.