aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2021-10-23 17:02:50 +0530
committerGitHub <noreply@github.com>2021-10-23 17:02:50 +0530
commitaac74ae4515aa1d746f46287029441f5a945c98e (patch)
tree9d23e06592ecd884d6b58dd089692d9e4224a3f9
parent21c8653e6815ca34574e783a5ce7ac783b188228 (diff)
Switch new PGP backend to use PGPainless (#1522)
* crypto-pgpainless: init * crypto-pgpainless: add an opinionated CryptoHandler impl * app: migrate to crypto-pgpainless * crypto-pgp: remove * github: remove now unused instrumentation tests job * crypto-common: fixup package names * wip(crypto-pgpainless): add `PGPKeyPair` and `PGPKeyManager` Signed-off-by: Aditya Wasan <adityawasan55@gmail.com> (cherry picked from commit 02d07e9e797a8600cc8c534a731dfffcc44cfdde) * crypto-pgpainless: use hex-encoded key IDs * crypto-pgpainless: replace legacy Gopenpgp-generated key file * crypto-pgpainless: fix CryptoConstants source set * crypto-pgpainless: fix tests * crypto-pgpainless: reinstate PGPKeyManager tests Co-authored-by: Aditya Wasan <adityawasan55@gmail.com>
-rw-r--r--.github/workflows/pull_request.yml62
-rw-r--r--app/build.gradle.kts2
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/injection/crypto/CryptoHandlerModule.kt6
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivityV2.kt10
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/crypto/DecryptActivityV2.kt10
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivityV2.kt13
-rw-r--r--crypto-common/api/crypto-common.api44
-rw-r--r--crypto-common/src/main/kotlin/dev/msfjarvis/aps/crypto/CryptoException.kt (renamed from crypto-common/src/main/kotlin/dev/msfjarvis/aps/data/crypto/CryptoException.kt)2
-rw-r--r--crypto-common/src/main/kotlin/dev/msfjarvis/aps/crypto/CryptoHandler.kt37
-rw-r--r--crypto-common/src/main/kotlin/dev/msfjarvis/aps/crypto/KeyManager.kt (renamed from crypto-common/src/main/kotlin/dev/msfjarvis/aps/data/crypto/KeyManager.kt)2
-rw-r--r--crypto-common/src/main/kotlin/dev/msfjarvis/aps/crypto/KeyPair.kt (renamed from crypto-common/src/main/kotlin/dev/msfjarvis/aps/data/crypto/KeyPair.kt)2
-rw-r--r--crypto-common/src/main/kotlin/dev/msfjarvis/aps/data/crypto/CryptoHandler.kt25
-rw-r--r--crypto-pgp/api/crypto-pgp.api25
-rw-r--r--crypto-pgp/build.gradle.kts29
-rw-r--r--crypto-pgp/src/androidTest/kotlin/dev/msfjarvis/aps/crypto/GPGKeyPairTest.kt52
-rw-r--r--crypto-pgp/src/androidTest/res/raw/private_key18
-rw-r--r--crypto-pgp/src/main/AndroidManifest.xml6
-rw-r--r--crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GPGKeyPair.kt28
-rw-r--r--crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GopenpgpCryptoHandler.kt49
-rw-r--r--crypto-pgpainless/api/crypto-pgpainless.api31
-rw-r--r--crypto-pgpainless/build.gradle.kts20
-rw-r--r--crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKeyManager.kt (renamed from crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GPGKeyManager.kt)39
-rw-r--r--crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKeyPair.kt31
-rw-r--r--crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPainlessCryptoHandler.kt69
-rw-r--r--crypto-pgpainless/src/test/kotlin/dev/msfjarvis/aps/crypto/CryptoConstants.kt (renamed from crypto-pgp/src/androidTest/kotlin/dev/msfjarvis/aps/crypto/utils/CryptoConstants.kt)4
-rw-r--r--crypto-pgpainless/src/test/kotlin/dev/msfjarvis/aps/crypto/PGPKeyManagerTest.kt (renamed from crypto-pgp/src/androidTest/kotlin/dev/msfjarvis/aps/crypto/GPGKeyManagerTest.kt)86
-rw-r--r--crypto-pgpainless/src/test/kotlin/dev/msfjarvis/aps/crypto/PGPKeyPairTest.kt23
-rw-r--r--crypto-pgpainless/src/test/resources/private_key26
-rw-r--r--gradle/libs.versions.toml2
-rw-r--r--settings.gradle.kts2
30 files changed, 353 insertions, 402 deletions
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index ead79571..7ecceb49 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -51,65 +51,3 @@ jobs:
with:
name: Test report
path: app/build/reports
-
- instrumentation-tests:
- runs-on: macos-11
- steps:
- - name: Checkout repository
- uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- with:
- fetch-depth: 0
-
- - name: Check if relevant files have changed
- uses: actions/github-script@a3e7071a34d7e1f219a8a4de9a5e0a34d1ee1293
- id: service-changed
- with:
- result-encoding: string
- script: |
- const script = require('.github/check-changed-files.js')
- return await script({github, context})
-
- - uses: actions/cache@c64c572235d810460d0d6876e9c705ad5002b353
- if: ${{ steps.service-changed.outputs.result == 'true' }}
- id: avd-cache
- with:
- path: |
- ~/.android/avd/*
- ~/.android/adb*
- key: avd-23
-
- - uses: actions/setup-java@d202f5dbf7256730fb690ec59f6381650114feb2
- if: ${{ steps.service-changed.outputs.result == 'true' }}
- with:
- java-version: '11'
-
- - name: Copy CI gradle.properties
- if: ${{ steps.service-changed.outputs.result == 'true' }}
- run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties
-
- - name: Create AVD and generate snapshot for caching
- uses: reactivecircus/android-emulator-runner@5de26e4bd23bf523e8a4b7f077df8bfb8e52b50e
- if: ${{ steps.avd-cache.outputs.cache-hit != 'true' }}
- with:
- api-level: 23
- force-avd-creation: false
- emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
- disable-animations: false
- script: echo "Generated AVD snapshot for caching"
-
- - name: Run screenshot tests
- uses: reactivecircus/android-emulator-runner@5de26e4bd23bf523e8a4b7f077df8bfb8e52b50e
- if: ${{ steps.service-changed.outputs.result == 'true' }}
- with:
- api-level: 23
- force-avd-creation: false
- emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
- disable-animations: true
- script: ./gradlew connectedCheck -PslimTests
-
- - name: (Fail-only) upload test report
- if: failure()
- uses: actions/upload-artifact@27121b0bdffd731efa15d66772be8dc71245d074
- with:
- name: Test report
- path: app/build/reports
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 32207759..54042fb7 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -64,7 +64,7 @@ dependencies {
implementation(libs.androidx.annotation)
coreLibraryDesugaring(libs.android.desugarJdkLibs)
implementation(projects.autofillParser)
- implementation(projects.cryptoPgp)
+ implementation(projects.cryptoPgpainless)
implementation(projects.formatCommon)
implementation(projects.openpgpKtx)
implementation(libs.androidx.activity.ktx)
diff --git a/app/src/main/java/dev/msfjarvis/aps/injection/crypto/CryptoHandlerModule.kt b/app/src/main/java/dev/msfjarvis/aps/injection/crypto/CryptoHandlerModule.kt
index fdd37bf3..63a860d1 100644
--- a/app/src/main/java/dev/msfjarvis/aps/injection/crypto/CryptoHandlerModule.kt
+++ b/app/src/main/java/dev/msfjarvis/aps/injection/crypto/CryptoHandlerModule.kt
@@ -10,8 +10,8 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.IntoSet
-import dev.msfjarvis.aps.data.crypto.CryptoHandler
-import dev.msfjarvis.aps.data.crypto.GopenpgpCryptoHandler
+import dev.msfjarvis.aps.crypto.CryptoHandler
+import dev.msfjarvis.aps.crypto.PGPainlessCryptoHandler
/**
* This module adds all [CryptoHandler] implementations into a Set which makes it easier to build
@@ -23,7 +23,7 @@ object CryptoHandlerModule {
@Provides
@IntoSet
fun providePgpCryptoHandler(): CryptoHandler {
- return GopenpgpCryptoHandler()
+ return PGPainlessCryptoHandler()
}
}
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivityV2.kt b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivityV2.kt
index bb2e6492..5351c5d2 100644
--- a/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivityV2.kt
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillDecryptActivityV2.kt
@@ -28,6 +28,7 @@ import dev.msfjarvis.aps.util.autofill.AutofillPreferences
import dev.msfjarvis.aps.util.autofill.AutofillResponseBuilder
import dev.msfjarvis.aps.util.autofill.DirectoryStructure
import dev.msfjarvis.aps.util.extensions.asLog
+import java.io.ByteArrayOutputStream
import java.io.File
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
@@ -130,11 +131,14 @@ class AutofillDecryptActivityV2 : AppCompatActivity() {
runCatching {
val crypto = cryptos.first { it.canHandle(file.absolutePath) }
withContext(Dispatchers.IO) {
+ val outputStream = ByteArrayOutputStream()
crypto.decrypt(
DecryptActivityV2.PRIV_KEY,
- DecryptActivityV2.PASS.toByteArray(charset = Charsets.UTF_8),
- encryptedInput.readBytes()
+ DecryptActivityV2.PASS,
+ encryptedInput,
+ outputStream,
)
+ outputStream
}
}
.onFailure { e ->
@@ -143,7 +147,7 @@ class AutofillDecryptActivityV2 : AppCompatActivity() {
}
.onSuccess { result ->
return runCatching {
- val entry = passwordEntryFactory.create(lifecycleScope, result)
+ val entry = passwordEntryFactory.create(lifecycleScope, result.toByteArray())
AutofillPreferences.credentialsFromStoreEntry(this, file, entry, directoryStructure)
}
.getOrElse { e ->
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/DecryptActivityV2.kt b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/DecryptActivityV2.kt
index b000d21d..403b8191 100644
--- a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/DecryptActivityV2.kt
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/DecryptActivityV2.kt
@@ -20,6 +20,7 @@ import dev.msfjarvis.aps.injection.password.PasswordEntryFactory
import dev.msfjarvis.aps.ui.adapters.FieldItemAdapter
import dev.msfjarvis.aps.util.extensions.unsafeLazy
import dev.msfjarvis.aps.util.extensions.viewBinding
+import java.io.ByteArrayOutputStream
import java.io.File
import javax.inject.Inject
import kotlin.time.Duration
@@ -126,19 +127,22 @@ class DecryptActivityV2 : BasePgpActivity() {
private fun decrypt() {
lifecycleScope.launch {
// TODO(msfjarvis): native methods are fallible, add error handling once out of testing
- val message = withContext(Dispatchers.IO) { File(fullPath).readBytes() }
+ val message = withContext(Dispatchers.IO) { File(fullPath).inputStream() }
val result =
withContext(Dispatchers.IO) {
val crypto = cryptos.first { it.canHandle(fullPath) }
+ val outputStream = ByteArrayOutputStream()
crypto.decrypt(
PRIV_KEY,
- PASS.toByteArray(charset = Charsets.UTF_8),
+ PASS,
message,
+ outputStream,
)
+ outputStream
}
startAutoDismissTimer()
- val entry = passwordEntryFactory.create(lifecycleScope, result)
+ val entry = passwordEntryFactory.create(lifecycleScope, result.toByteArray())
passwordEntry = entry
invalidateOptionsMenu()
diff --git a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivityV2.kt b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivityV2.kt
index c2cc5b10..095ce53e 100644
--- a/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivityV2.kt
+++ b/app/src/main/java/dev/msfjarvis/aps/ui/crypto/PasswordCreationActivityV2.kt
@@ -43,6 +43,7 @@ import dev.msfjarvis.aps.util.extensions.snackbar
import dev.msfjarvis.aps.util.extensions.unsafeLazy
import dev.msfjarvis.aps.util.extensions.viewBinding
import dev.msfjarvis.aps.util.settings.PreferenceKeys
+import java.io.ByteArrayOutputStream
import java.io.File
import java.io.IOException
import javax.inject.Inject
@@ -319,7 +320,15 @@ class PasswordCreationActivityV2 : BasePgpActivity() {
runCatching {
val crypto = cryptos.first { it.canHandle(path) }
val result =
- withContext(Dispatchers.IO) { crypto.encrypt(PUB_KEY, content.encodeToByteArray()) }
+ withContext(Dispatchers.IO) {
+ val outputStream = ByteArrayOutputStream()
+ crypto.encrypt(
+ listOf(PUB_KEY),
+ content.byteInputStream(),
+ outputStream,
+ )
+ outputStream
+ }
val file = File(path)
// If we're not editing, this file should not already exist!
// Additionally, if we were editing and the incoming and outgoing
@@ -336,7 +345,7 @@ class PasswordCreationActivityV2 : BasePgpActivity() {
return@runCatching
}
- withContext(Dispatchers.IO) { file.outputStream().use { it.write(result) } }
+ withContext(Dispatchers.IO) { file.writeBytes(result.toByteArray()) }
// associate the new password name with the last name's timestamp in
// history
diff --git a/crypto-common/api/crypto-common.api b/crypto-common/api/crypto-common.api
index 6468ea9b..6d137885 100644
--- a/crypto-common/api/crypto-common.api
+++ b/crypto-common/api/crypto-common.api
@@ -1,63 +1,63 @@
-public abstract class dev/msfjarvis/aps/data/crypto/CryptoException : java/lang/Exception {
+public abstract class dev/msfjarvis/aps/crypto/CryptoException : java/lang/Exception {
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}
-public abstract interface class dev/msfjarvis/aps/data/crypto/CryptoHandler {
+public abstract interface class dev/msfjarvis/aps/crypto/CryptoHandler {
public abstract fun canHandle (Ljava/lang/String;)Z
- public abstract fun decrypt (Ljava/lang/String;[B[B)[B
- public abstract fun encrypt (Ljava/lang/String;[B)[B
+ public abstract fun decrypt (Ljava/lang/String;Ljava/lang/String;Ljava/io/InputStream;Ljava/io/OutputStream;)V
+ public abstract fun encrypt (Ljava/util/List;Ljava/io/InputStream;Ljava/io/OutputStream;)V
}
-public abstract interface class dev/msfjarvis/aps/data/crypto/KeyManager {
- public abstract fun addKey (Ldev/msfjarvis/aps/data/crypto/KeyPair;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
+public abstract interface class dev/msfjarvis/aps/crypto/KeyManager {
+ public abstract fun addKey (Ldev/msfjarvis/aps/crypto/KeyPair;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun canHandle (Ljava/lang/String;)Z
public abstract fun getAllKeys (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun getKeyById (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public abstract fun removeKey (Ldev/msfjarvis/aps/data/crypto/KeyPair;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun removeKey (Ldev/msfjarvis/aps/crypto/KeyPair;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
-public final class dev/msfjarvis/aps/data/crypto/KeyManager$DefaultImpls {
- public static synthetic fun addKey$default (Ldev/msfjarvis/aps/data/crypto/KeyManager;Ldev/msfjarvis/aps/data/crypto/KeyPair;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
+public final class dev/msfjarvis/aps/crypto/KeyManager$DefaultImpls {
+ public static synthetic fun addKey$default (Ldev/msfjarvis/aps/crypto/KeyManager;Ldev/msfjarvis/aps/crypto/KeyPair;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
}
-public abstract class dev/msfjarvis/aps/data/crypto/KeyManagerException : dev/msfjarvis/aps/data/crypto/CryptoException {
+public abstract class dev/msfjarvis/aps/crypto/KeyManagerException : dev/msfjarvis/aps/crypto/CryptoException {
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}
-public final class dev/msfjarvis/aps/data/crypto/KeyManagerException$KeyAlreadyExistsException : dev/msfjarvis/aps/data/crypto/KeyManagerException {
+public final class dev/msfjarvis/aps/crypto/KeyManagerException$KeyAlreadyExistsException : dev/msfjarvis/aps/crypto/KeyManagerException {
public fun <init> (Ljava/lang/String;)V
}
-public final class dev/msfjarvis/aps/data/crypto/KeyManagerException$KeyDeletionFailedException : dev/msfjarvis/aps/data/crypto/KeyManagerException {
- public static final field INSTANCE Ldev/msfjarvis/aps/data/crypto/KeyManagerException$KeyDeletionFailedException;
+public final class dev/msfjarvis/aps/crypto/KeyManagerException$KeyDeletionFailedException : dev/msfjarvis/aps/crypto/KeyManagerException {
+ public static final field INSTANCE Ldev/msfjarvis/aps/crypto/KeyManagerException$KeyDeletionFailedException;
}
-public final class dev/msfjarvis/aps/data/crypto/KeyManagerException$KeyDirectoryUnavailableException : dev/msfjarvis/aps/data/crypto/KeyManagerException {
- public static final field INSTANCE Ldev/msfjarvis/aps/data/crypto/KeyManagerException$KeyDirectoryUnavailableException;
+public final class dev/msfjarvis/aps/crypto/KeyManagerException$KeyDirectoryUnavailableException : dev/msfjarvis/aps/crypto/KeyManagerException {
+ public static final field INSTANCE Ldev/msfjarvis/aps/crypto/KeyManagerException$KeyDirectoryUnavailableException;
}
-public final class dev/msfjarvis/aps/data/crypto/KeyManagerException$KeyNotFoundException : dev/msfjarvis/aps/data/crypto/KeyManagerException {
+public final class dev/msfjarvis/aps/crypto/KeyManagerException$KeyNotFoundException : dev/msfjarvis/aps/crypto/KeyManagerException {
public fun <init> (Ljava/lang/String;)V
}
-public final class dev/msfjarvis/aps/data/crypto/KeyManagerException$NoKeysAvailableException : dev/msfjarvis/aps/data/crypto/KeyManagerException {
- public static final field INSTANCE Ldev/msfjarvis/aps/data/crypto/KeyManagerException$NoKeysAvailableException;
+public final class dev/msfjarvis/aps/crypto/KeyManagerException$NoKeysAvailableException : dev/msfjarvis/aps/crypto/KeyManagerException {
+ public static final field INSTANCE Ldev/msfjarvis/aps/crypto/KeyManagerException$NoKeysAvailableException;
}
-public abstract interface class dev/msfjarvis/aps/data/crypto/KeyPair {
+public abstract interface class dev/msfjarvis/aps/crypto/KeyPair {
public abstract fun getKeyId ()Ljava/lang/String;
public abstract fun getPrivateKey ()[B
public abstract fun getPublicKey ()[B
}
-public abstract class dev/msfjarvis/aps/data/crypto/KeyPairException : dev/msfjarvis/aps/data/crypto/CryptoException {
+public abstract class dev/msfjarvis/aps/crypto/KeyPairException : dev/msfjarvis/aps/crypto/CryptoException {
public synthetic fun <init> (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}
-public final class dev/msfjarvis/aps/data/crypto/KeyPairException$PrivateKeyUnavailableException : dev/msfjarvis/aps/data/crypto/KeyPairException {
- public static final field INSTANCE Ldev/msfjarvis/aps/data/crypto/KeyPairException$PrivateKeyUnavailableException;
+public final class dev/msfjarvis/aps/crypto/KeyPairException$PrivateKeyUnavailableException : dev/msfjarvis/aps/crypto/KeyPairException {
+ public static final field INSTANCE Ldev/msfjarvis/aps/crypto/KeyPairException$PrivateKeyUnavailableException;
}
diff --git a/crypto-common/src/main/kotlin/dev/msfjarvis/aps/data/crypto/CryptoException.kt b/crypto-common/src/main/kotlin/dev/msfjarvis/aps/crypto/CryptoException.kt
index 6a73d381..34e64d5f 100644
--- a/crypto-common/src/main/kotlin/dev/msfjarvis/aps/data/crypto/CryptoException.kt
+++ b/crypto-common/src/main/kotlin/dev/msfjarvis/aps/crypto/CryptoException.kt
@@ -1,4 +1,4 @@
-package dev.msfjarvis.aps.data.crypto
+package dev.msfjarvis.aps.crypto
public sealed class CryptoException(message: String? = null) : Exception(message)
diff --git a/crypto-common/src/main/kotlin/dev/msfjarvis/aps/crypto/CryptoHandler.kt b/crypto-common/src/main/kotlin/dev/msfjarvis/aps/crypto/CryptoHandler.kt
new file mode 100644
index 00000000..c64e9c9b
--- /dev/null
+++ b/crypto-common/src/main/kotlin/dev/msfjarvis/aps/crypto/CryptoHandler.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.crypto
+
+import java.io.InputStream
+import java.io.OutputStream
+
+/** Generic interface to implement cryptographic operations on top of. */
+public interface CryptoHandler {
+
+ /**
+ * Decrypt the given [ciphertextStream] using a [privateKey] and [password], and writes the
+ * resultant plaintext to [outputStream].
+ */
+ public fun decrypt(
+ privateKey: String,
+ password: String,
+ ciphertextStream: InputStream,
+ outputStream: OutputStream,
+ )
+
+ /**
+ * Encrypt the given [plaintextStream] to the provided [pubKeys], and writes the encrypted
+ * ciphertext to [outputStream].
+ */
+ public fun encrypt(
+ pubKeys: List<String>,
+ plaintextStream: InputStream,
+ outputStream: OutputStream,
+ )
+
+ /** Given a [fileName], return whether this instance can handle it. */
+ public fun canHandle(fileName: String): Boolean
+}
diff --git a/crypto-common/src/main/kotlin/dev/msfjarvis/aps/data/crypto/KeyManager.kt b/crypto-common/src/main/kotlin/dev/msfjarvis/aps/crypto/KeyManager.kt
index b5ba881e..2f901354 100644
--- a/crypto-common/src/main/kotlin/dev/msfjarvis/aps/data/crypto/KeyManager.kt
+++ b/crypto-common/src/main/kotlin/dev/msfjarvis/aps/crypto/KeyManager.kt
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: GPL-3.0-only
*/
-package dev.msfjarvis.aps.data.crypto
+package dev.msfjarvis.aps.crypto
import com.github.michaelbull.result.Result
diff --git a/crypto-common/src/main/kotlin/dev/msfjarvis/aps/data/crypto/KeyPair.kt b/crypto-common/src/main/kotlin/dev/msfjarvis/aps/crypto/KeyPair.kt
index e2362612..b8dec216 100644
--- a/crypto-common/src/main/kotlin/dev/msfjarvis/aps/data/crypto/KeyPair.kt
+++ b/crypto-common/src/main/kotlin/dev/msfjarvis/aps/crypto/KeyPair.kt
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: GPL-3.0-only
*/
-package dev.msfjarvis.aps.data.crypto
+package dev.msfjarvis.aps.crypto
/** Defines expectations for a keypair used in public key cryptography. */
public interface KeyPair {
diff --git a/crypto-common/src/main/kotlin/dev/msfjarvis/aps/data/crypto/CryptoHandler.kt b/crypto-common/src/main/kotlin/dev/msfjarvis/aps/data/crypto/CryptoHandler.kt
deleted file mode 100644
index 453613a4..00000000
--- a/crypto-common/src/main/kotlin/dev/msfjarvis/aps/data/crypto/CryptoHandler.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-package dev.msfjarvis.aps.data.crypto
-
-/** Generic interface to implement cryptographic operations on top of. */
-public interface CryptoHandler {
-
- /**
- * Decrypt the given [ciphertext] using a [privateKey] and [passphrase], returning a [ByteArray]
- * corresponding to the decrypted plaintext.
- */
- public fun decrypt(privateKey: String, passphrase: ByteArray, ciphertext: ByteArray): ByteArray
-
- /**
- * Encrypt the given [plaintext] to the provided [publicKey], returning the encrypted ciphertext
- * as a [ByteArray]
- */
- public fun encrypt(publicKey: String, plaintext: ByteArray): ByteArray
-
- /** Given a [fileName], return whether this instance can handle it. */
- public fun canHandle(fileName: String): Boolean
-}
diff --git a/crypto-pgp/api/crypto-pgp.api b/crypto-pgp/api/crypto-pgp.api
deleted file mode 100644
index c9b2dde7..00000000
--- a/crypto-pgp/api/crypto-pgp.api
+++ /dev/null
@@ -1,25 +0,0 @@
-public final class dev/msfjarvis/aps/data/crypto/GPGKeyManager : dev/msfjarvis/aps/data/crypto/KeyManager {
- public fun <init> (Ljava/lang/String;Lkotlinx/coroutines/CoroutineDispatcher;)V
- public fun addKey (Ldev/msfjarvis/aps/data/crypto/GPGKeyPair;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public synthetic fun addKey (Ldev/msfjarvis/aps/data/crypto/KeyPair;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 fun removeKey (Ldev/msfjarvis/aps/data/crypto/GPGKeyPair;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public synthetic fun removeKey (Ldev/msfjarvis/aps/data/crypto/KeyPair;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
-}
-
-public final class dev/msfjarvis/aps/data/crypto/GPGKeyPair : dev/msfjarvis/aps/data/crypto/KeyPair {
- public fun <init> (Lcom/proton/Gopenpgp/crypto/Key;)V
- public fun getKeyId ()Ljava/lang/String;
- public fun getPrivateKey ()[B
- public fun getPublicKey ()[B
-}
-
-public final class dev/msfjarvis/aps/data/crypto/GopenpgpCryptoHandler : dev/msfjarvis/aps/data/crypto/CryptoHandler {
- public fun <init> ()V
- public fun canHandle (Ljava/lang/String;)Z
- public fun decrypt (Ljava/lang/String;[B[B)[B
- public fun encrypt (Ljava/lang/String;[B)[B
-}
-
diff --git a/crypto-pgp/build.gradle.kts b/crypto-pgp/build.gradle.kts
deleted file mode 100644
index 95542b1c..00000000
--- a/crypto-pgp/build.gradle.kts
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-plugins {
- id("com.android.library")
- kotlin("android")
- `aps-plugin`
-}
-
-android {
- defaultConfig {
- testApplicationId = "dev.msfjarvis.aps.cryptopgp.test"
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- }
-}
-
-dependencies {
- api(projects.cryptoCommon)
- implementation(libs.androidx.annotation)
- implementation(libs.aps.gopenpgp)
- implementation(libs.dagger.hilt.core)
- implementation(libs.kotlin.coroutines.core)
- implementation(libs.thirdparty.kotlinResult)
- androidTestImplementation(libs.bundles.testDependencies)
- androidTestImplementation(libs.kotlin.coroutines.test)
- androidTestImplementation(libs.bundles.androidTestDependencies)
-}
diff --git a/crypto-pgp/src/androidTest/kotlin/dev/msfjarvis/aps/crypto/GPGKeyPairTest.kt b/crypto-pgp/src/androidTest/kotlin/dev/msfjarvis/aps/crypto/GPGKeyPairTest.kt
deleted file mode 100644
index 2340d9a5..00000000
--- a/crypto-pgp/src/androidTest/kotlin/dev/msfjarvis/aps/crypto/GPGKeyPairTest.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.test.platform.app.InstrumentationRegistry
-import com.proton.Gopenpgp.crypto.Key
-import dev.msfjarvis.aps.crypto.utils.CryptoConstants
-import dev.msfjarvis.aps.cryptopgp.test.R
-import dev.msfjarvis.aps.data.crypto.GPGKeyPair
-import dev.msfjarvis.aps.data.crypto.KeyPairException
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
-import org.junit.Test
-
-public class GPGKeyPairTest {
-
- @Test
- public fun testIfKeyIdIsCorrect() {
- val gpgKey = Key(getKey())
- val keyPair = GPGKeyPair(gpgKey)
-
- assertEquals(CryptoConstants.KEY_ID, keyPair.getKeyId())
- }
-
- @Test
- public fun testBuildingKeyPairWithoutPrivateKey() {
- assertFailsWith<KeyPairException.PrivateKeyUnavailableException>(
- "GPGKeyPair does not have a private sub key"
- ) {
- // Get public key object from private key
- val gpgKey = Key(getKey()).toPublic()
- // Try creating a KeyPair from public key
- val keyPair = GPGKeyPair(gpgKey)
-
- keyPair.getPrivateKey()
- }
- }
-
- private companion object {
-
- fun getKey(): String =
- InstrumentationRegistry.getInstrumentation()
- .context
- .resources
- .openRawResource(R.raw.private_key)
- .readBytes()
- .decodeToString()
- }
-}
diff --git a/crypto-pgp/src/androidTest/res/raw/private_key b/crypto-pgp/src/androidTest/res/raw/private_key
deleted file mode 100644
index 5a4f436c..00000000
--- a/crypto-pgp/src/androidTest/res/raw/private_key
+++ /dev/null
@@ -1,18 +0,0 @@
------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----- \ No newline at end of file
diff --git a/crypto-pgp/src/main/AndroidManifest.xml b/crypto-pgp/src/main/AndroidManifest.xml
deleted file mode 100644
index f72b702d..00000000
--- a/crypto-pgp/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
- ~ SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception
- -->
-
-<manifest package="dev.msfjarvis.aps.cryptopgp"></manifest>
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
deleted file mode 100644
index 2dbe8689..00000000
--- a/crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GPGKeyPair.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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
- }
-}
diff --git a/crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GopenpgpCryptoHandler.kt b/crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GopenpgpCryptoHandler.kt
deleted file mode 100644
index 5d14b160..00000000
--- a/crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GopenpgpCryptoHandler.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.Crypto
-import com.proton.Gopenpgp.helper.Helper
-import javax.inject.Inject
-
-/** Gopenpgp backed implementation of [CryptoHandler]. */
-public class GopenpgpCryptoHandler @Inject constructor() : CryptoHandler {
-
- /**
- * Decrypt the given [ciphertext] using the given PGP [privateKey] and corresponding [passphrase].
- */
- override fun decrypt(
- privateKey: String,
- passphrase: ByteArray,
- ciphertext: ByteArray,
- ): ByteArray {
- // Decode the incoming cipher into a string and try to guess if it's armored.
- val cipherString = ciphertext.decodeToString()
- val isArmor = cipherString.startsWith("-----BEGIN PGP MESSAGE-----")
- val message =
- if (isArmor) {
- Crypto.newPGPMessageFromArmored(cipherString)
- } else {
- Crypto.newPGPMessage(ciphertext)
- }
- return Helper.decryptBinaryMessageArmored(
- privateKey,
- passphrase,
- message.armored,
- )
- }
-
- override fun encrypt(publicKey: String, plaintext: ByteArray): ByteArray {
- return Helper.encryptBinaryMessage(
- publicKey,
- plaintext,
- )
- }
-
- override fun canHandle(fileName: String): Boolean {
- return fileName.split('.').last() == "gpg"
- }
-}
diff --git a/crypto-pgpainless/api/crypto-pgpainless.api b/crypto-pgpainless/api/crypto-pgpainless.api
new file mode 100644
index 00000000..1c59ca1f
--- /dev/null
+++ b/crypto-pgpainless/api/crypto-pgpainless.api
@@ -0,0 +1,31 @@
+public final class dev/msfjarvis/aps/crypto/PGPKeyManager : dev/msfjarvis/aps/crypto/KeyManager {
+ public static final field Companion Ldev/msfjarvis/aps/crypto/PGPKeyManager$Companion;
+ 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 static final fun makeKey (Ljava/lang/String;)Ldev/msfjarvis/aps/crypto/PGPKeyPair;
+ 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/PGPKeyManager$Companion {
+ public final fun makeKey (Ljava/lang/String;)Ldev/msfjarvis/aps/crypto/PGPKeyPair;
+}
+
+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
+ public fun decrypt (Ljava/lang/String;Ljava/lang/String;Ljava/io/InputStream;Ljava/io/OutputStream;)V
+ public fun encrypt (Ljava/util/List;Ljava/io/InputStream;Ljava/io/OutputStream;)V
+}
+
diff --git a/crypto-pgpainless/build.gradle.kts b/crypto-pgpainless/build.gradle.kts
new file mode 100644
index 00000000..37040e80
--- /dev/null
+++ b/crypto-pgpainless/build.gradle.kts
@@ -0,0 +1,20 @@
+/*
+ * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+plugins {
+ kotlin("jvm")
+ `aps-plugin`
+}
+
+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)
+}
diff --git a/crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GPGKeyManager.kt b/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKeyManager.kt
index 478d2700..fd886843 100644
--- a/crypto-pgp/src/main/kotlin/dev/msfjarvis/aps/data/crypto/GPGKeyManager.kt
+++ b/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKeyManager.kt
@@ -3,22 +3,24 @@
* SPDX-License-Identifier: GPL-3.0-only
*/
-package dev.msfjarvis.aps.data.crypto
+package dev.msfjarvis.aps.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
+import org.pgpainless.PGPainless
-public class GPGKeyManager(filesDir: String, private val dispatcher: CoroutineDispatcher) :
- KeyManager<GPGKeyPair> {
+public class PGPKeyManager(
+ filesDir: String,
+ private val dispatcher: CoroutineDispatcher,
+) : KeyManager<PGPKeyPair> {
private val keyDir = File(filesDir, KEY_DIR_NAME)
- override suspend fun addKey(key: GPGKeyPair, replace: Boolean): Result<GPGKeyPair, Throwable> =
+ override suspend fun addKey(key: PGPKeyPair, replace: Boolean): Result<PGPKeyPair, Throwable> =
withContext(dispatcher) {
runCatching {
if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
@@ -35,7 +37,7 @@ public class GPGKeyManager(filesDir: String, private val dispatcher: CoroutineDi
}
}
- override suspend fun removeKey(key: GPGKeyPair): Result<GPGKeyPair, Throwable> =
+ override suspend fun removeKey(key: PGPKeyPair): Result<PGPKeyPair, Throwable> =
withContext(dispatcher) {
runCatching {
if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
@@ -48,7 +50,7 @@ public class GPGKeyManager(filesDir: String, private val dispatcher: CoroutineDi
}
}
- override suspend fun getKeyById(id: String): Result<GPGKeyPair, Throwable> =
+ override suspend fun getKeyById(id: String): Result<PGPKeyPair, Throwable> =
withContext(dispatcher) {
runCatching {
if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
@@ -56,7 +58,9 @@ public class GPGKeyManager(filesDir: String, private val dispatcher: CoroutineDi
if (keys.isNullOrEmpty()) throw KeyManagerException.NoKeysAvailableException
for (keyFile in keys) {
- val keyPair = GPGKeyPair(Crypto.newKeyFromArmored(keyFile.readText()))
+ val secretKeyRing = PGPainless.readKeyRing().secretKeyRing(keyFile.inputStream())
+ val secretKey = secretKeyRing.secretKey
+ val keyPair = PGPKeyPair(secretKey)
if (keyPair.getKeyId() == id) return@runCatching keyPair
}
@@ -64,14 +68,21 @@ public class GPGKeyManager(filesDir: String, private val dispatcher: CoroutineDi
}
}
- override suspend fun getAllKeys(): Result<List<GPGKeyPair>, Throwable> =
+ 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 { GPGKeyPair(Crypto.newKeyFromArmored(it.readText())) }.toList()
+ keys
+ .map { keyFile ->
+ val secretKeyRing = PGPainless.readKeyRing().secretKeyRing(keyFile.inputStream())
+ val secretKey = secretKeyRing.secretKey
+
+ PGPKeyPair(secretKey)
+ }
+ .toList()
}
}
@@ -85,11 +96,17 @@ public class GPGKeyManager(filesDir: String, private val dispatcher: CoroutineDi
return keyDir.exists() || keyDir.mkdirs()
}
- internal companion object {
+ public 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"
+
+ @JvmStatic
+ public fun makeKey(armoredKey: String): PGPKeyPair {
+ val secretKey = PGPainless.readKeyRing().secretKeyRing(armoredKey).secretKey
+ return PGPKeyPair(secretKey)
+ }
}
}
diff --git a/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKeyPair.kt b/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKeyPair.kt
new file mode 100644
index 00000000..03bcf515
--- /dev/null
+++ b/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPKeyPair.kt
@@ -0,0 +1,31 @@
+/*
+ * 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 {
+ var keyId = secretKey.keyID.toString(radix = 16)
+ if (keyId.length < KEY_ID_LENGTH) keyId = "0$keyId"
+ return keyId
+ }
+
+ private companion object {
+ private const val KEY_ID_LENGTH = 16
+ }
+}
diff --git a/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPainlessCryptoHandler.kt b/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPainlessCryptoHandler.kt
new file mode 100644
index 00000000..3276b995
--- /dev/null
+++ b/crypto-pgpainless/src/main/kotlin/dev/msfjarvis/aps/crypto/PGPainlessCryptoHandler.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.crypto
+
+import java.io.ByteArrayInputStream
+import java.io.InputStream
+import java.io.OutputStream
+import javax.inject.Inject
+import org.bouncycastle.bcpg.ArmoredInputStream
+import org.bouncycastle.openpgp.PGPSecretKeyRingCollection
+import org.pgpainless.PGPainless
+import org.pgpainless.decryption_verification.ConsumerOptions
+import org.pgpainless.encryption_signing.EncryptionOptions
+import org.pgpainless.encryption_signing.ProducerOptions
+import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector
+import org.pgpainless.util.Passphrase
+
+public class PGPainlessCryptoHandler @Inject constructor() : CryptoHandler {
+
+ public override fun decrypt(
+ privateKey: String,
+ password: String,
+ ciphertextStream: InputStream,
+ outputStream: OutputStream,
+ ) {
+ val pgpSecretKeyRing = PGPainless.readKeyRing().secretKeyRing(privateKey)
+ val keyringCollection = PGPSecretKeyRingCollection(listOf(pgpSecretKeyRing))
+ val protector =
+ PasswordBasedSecretKeyRingProtector.forKey(
+ pgpSecretKeyRing,
+ Passphrase.fromPassword(password)
+ )
+ PGPainless.decryptAndOrVerify()
+ .onInputStream(ciphertextStream)
+ .withOptions(
+ ConsumerOptions()
+ .addDecryptionKeys(keyringCollection, protector)
+ .addDecryptionPassphrase(Passphrase.fromPassword(password))
+ )
+ .use { decryptionStream -> decryptionStream.copyTo(outputStream) }
+ }
+
+ public override fun encrypt(
+ pubKeys: List<String>,
+ plaintextStream: InputStream,
+ outputStream: OutputStream,
+ ) {
+ val pubKeysStream = ByteArrayInputStream(pubKeys.joinToString("\n").toByteArray())
+ val publicKeyRingCollection =
+ pubKeysStream.use {
+ ArmoredInputStream(it).use { armoredInputStream ->
+ PGPainless.readKeyRing().publicKeyRingCollection(armoredInputStream)
+ }
+ }
+ val encOpt = EncryptionOptions().apply { publicKeyRingCollection.forEach { addRecipient(it) } }
+ val prodOpt = ProducerOptions.encrypt(encOpt).setAsciiArmor(true)
+ PGPainless.encryptAndOrSign().onOutputStream(outputStream).withOptions(prodOpt).use {
+ encryptionStream ->
+ plaintextStream.copyTo(encryptionStream)
+ }
+ }
+
+ public override fun canHandle(fileName: String): Boolean {
+ return fileName.split('.').lastOrNull() == "gpg"
+ }
+}
diff --git a/crypto-pgp/src/androidTest/kotlin/dev/msfjarvis/aps/crypto/utils/CryptoConstants.kt b/crypto-pgpainless/src/test/kotlin/dev/msfjarvis/aps/crypto/CryptoConstants.kt
index 873f7105..fad5308c 100644
--- a/crypto-pgp/src/androidTest/kotlin/dev/msfjarvis/aps/crypto/utils/CryptoConstants.kt
+++ b/crypto-pgpainless/src/test/kotlin/dev/msfjarvis/aps/crypto/CryptoConstants.kt
@@ -3,12 +3,12 @@
* SPDX-License-Identifier: GPL-3.0-only
*/
-package dev.msfjarvis.aps.crypto.utils
+package dev.msfjarvis.aps.crypto
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"
+ internal const val KEY_ID = "08edf7567183ce27"
}
diff --git a/crypto-pgp/src/androidTest/kotlin/dev/msfjarvis/aps/crypto/GPGKeyManagerTest.kt b/crypto-pgpainless/src/test/kotlin/dev/msfjarvis/aps/crypto/PGPKeyManagerTest.kt
index 80a13eb5..12bb85ad 100644
--- a/crypto-pgp/src/androidTest/kotlin/dev/msfjarvis/aps/crypto/GPGKeyManagerTest.kt
+++ b/crypto-pgpainless/src/test/kotlin/dev/msfjarvis/aps/crypto/PGPKeyManagerTest.kt
@@ -1,59 +1,44 @@
package dev.msfjarvis.aps.crypto
-import androidx.test.platform.app.InstrumentationRegistry
import com.github.michaelbull.result.unwrap
import com.github.michaelbull.result.unwrapError
-import com.proton.Gopenpgp.crypto.Key
-import dev.msfjarvis.aps.crypto.utils.CryptoConstants
-import dev.msfjarvis.aps.cryptopgp.test.R
-import dev.msfjarvis.aps.data.crypto.GPGKeyManager
-import dev.msfjarvis.aps.data.crypto.GPGKeyPair
-import dev.msfjarvis.aps.data.crypto.KeyManagerException
import java.io.File
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
-import org.junit.After
-import org.junit.Before
+import org.junit.Rule
import org.junit.Test
+import org.junit.rules.TemporaryFolder
@OptIn(ExperimentalCoroutinesApi::class)
-public class GPGKeyManagerTest {
+public class PGPKeyManagerTest {
- private val testCoroutineDispatcher = TestCoroutineDispatcher()
- private lateinit var gpgKeyManager: GPGKeyManager
- private lateinit var key: GPGKeyPair
-
- @Before
- public fun setup() {
- gpgKeyManager = GPGKeyManager(getFilesDir().absolutePath, testCoroutineDispatcher)
- key = GPGKeyPair(Key(getKey()))
+ @get:Rule public val temporaryFolder: TemporaryFolder = TemporaryFolder()
+ private val filesDir by lazy(LazyThreadSafetyMode.NONE) { temporaryFolder.root }
+ private val keysDir by lazy(LazyThreadSafetyMode.NONE) {
+ File(filesDir, PGPKeyManager.KEY_DIR_NAME)
}
-
- @After
- public fun tearDown() {
- val filesDir = getFilesDir()
- val keysDir = File(filesDir, GPGKeyManager.KEY_DIR_NAME)
-
- keysDir.deleteRecursively()
+ private val testCoroutineDispatcher = TestCoroutineDispatcher()
+ private val keyManager by lazy(LazyThreadSafetyMode.NONE) {
+ PGPKeyManager(filesDir.absolutePath, testCoroutineDispatcher)
}
+ private val key = PGPKeyManager.makeKey(getArmoredKey())
@Test
public fun testAddingKey() {
runBlockingTest {
// Check if the key id returned is correct
- val keyId = gpgKeyManager.addKey(key).unwrap().getKeyId()
+ val keyId = keyManager.addKey(key).unwrap().getKeyId()
assertEquals(CryptoConstants.KEY_ID, keyId)
// Check if the keys directory have one file
- val keysDir = File(getFilesDir(), GPGKeyManager.KEY_DIR_NAME)
- assertEquals(1, keysDir.list()?.size)
+ assertEquals(1, filesDir.list()?.size)
// Check if the file name is correct
val keyFile = keysDir.listFiles()?.first()
- assertEquals(keyFile?.name, "$keyId.${GPGKeyManager.KEY_EXTENSION}")
+ assertEquals(keyFile?.name, "$keyId.${PGPKeyManager.KEY_EXTENSION}")
}
}
@@ -61,8 +46,8 @@ public class GPGKeyManagerTest {
public fun testAddingKeyWithoutReplaceFlag() {
runBlockingTest {
// Check adding the keys twice
- gpgKeyManager.addKey(key, false).unwrap()
- val error = gpgKeyManager.addKey(key, false).unwrapError()
+ keyManager.addKey(key, false).unwrap()
+ val error = keyManager.addKey(key, false).unwrapError()
assertIs<KeyManagerException.KeyAlreadyExistsException>(error)
}
@@ -72,8 +57,8 @@ public class GPGKeyManagerTest {
public fun testAddingKeyWithReplaceFlag() {
runBlockingTest {
// Check adding the keys twice
- gpgKeyManager.addKey(key, true).unwrap()
- val keyId = gpgKeyManager.addKey(key, true).unwrap().getKeyId()
+ keyManager.addKey(key, true).unwrap()
+ val keyId = keyManager.addKey(key, true).unwrap().getKeyId()
assertEquals(CryptoConstants.KEY_ID, keyId)
}
@@ -83,14 +68,14 @@ public class GPGKeyManagerTest {
public fun testRemovingKey() {
runBlockingTest {
// Add key using KeyManager
- gpgKeyManager.addKey(key).unwrap()
+ keyManager.addKey(key).unwrap()
// Check if the key id returned is correct
- val keyId = gpgKeyManager.removeKey(key).unwrap().getKeyId()
+ val keyId = keyManager.removeKey(key).unwrap().getKeyId()
assertEquals(CryptoConstants.KEY_ID, keyId)
// Check if the keys directory have 0 files
- val keysDir = File(getFilesDir(), GPGKeyManager.KEY_DIR_NAME)
+ val keysDir = File(filesDir, PGPKeyManager.KEY_DIR_NAME)
assertEquals(0, keysDir.list()?.size)
}
}
@@ -99,10 +84,10 @@ public class GPGKeyManagerTest {
public fun testGetExistingKey() {
runBlockingTest {
// Add key using KeyManager
- gpgKeyManager.addKey(key).unwrap()
+ keyManager.addKey(key).unwrap()
// Check returned key id matches the expected id and the created key id
- val returnedKeyPair = gpgKeyManager.getKeyById(key.getKeyId()).unwrap()
+ val returnedKeyPair = keyManager.getKeyById(key.getKeyId()).unwrap()
assertEquals(CryptoConstants.KEY_ID, key.getKeyId())
assertEquals(key.getKeyId(), returnedKeyPair.getKeyId())
}
@@ -112,12 +97,12 @@ public class GPGKeyManagerTest {
public fun testGetNonExistentKey() {
runBlockingTest {
// Add key using KeyManager
- gpgKeyManager.addKey(key).unwrap()
+ keyManager.addKey(key).unwrap()
val randomKeyId = "0x123456789"
// Check returned key
- val error = gpgKeyManager.getKeyById(randomKeyId).unwrapError()
+ val error = keyManager.getKeyById(randomKeyId).unwrapError()
assertIs<KeyManagerException.KeyNotFoundException>(error)
assertEquals("No key found with id: $randomKeyId", error.message)
}
@@ -127,7 +112,7 @@ public class GPGKeyManagerTest {
public fun testFindKeysWithoutAdding() {
runBlockingTest {
// Check returned key
- val error = gpgKeyManager.getKeyById("0x123456789").unwrapError()
+ val error = keyManager.getKeyById("0x123456789").unwrapError()
assertIs<KeyManagerException.NoKeysAvailableException>(error)
assertEquals("No keys were found", error.message)
}
@@ -138,28 +123,17 @@ public class GPGKeyManagerTest {
runBlockingTest {
// TODO: Should we check for more than 1 keys?
// Check if KeyManager returns no key
- val noKeyList = gpgKeyManager.getAllKeys().unwrap()
+ val noKeyList = keyManager.getAllKeys().unwrap()
assertEquals(0, noKeyList.size)
// Add key using KeyManager
- gpgKeyManager.addKey(key).unwrap()
+ keyManager.addKey(key).unwrap()
// Check if KeyManager returns one key
- val singleKeyList = gpgKeyManager.getAllKeys().unwrap()
+ val singleKeyList = keyManager.getAllKeys().unwrap()
assertEquals(1, singleKeyList.size)
}
}
- private companion object {
-
- fun getFilesDir(): File = InstrumentationRegistry.getInstrumentation().context.filesDir
-
- fun getKey(): String =
- InstrumentationRegistry.getInstrumentation()
- .context
- .resources
- .openRawResource(R.raw.private_key)
- .readBytes()
- .decodeToString()
- }
+ private fun getArmoredKey() = this::class.java.classLoader.getResource("private_key").readText()
}
diff --git a/crypto-pgpainless/src/test/kotlin/dev/msfjarvis/aps/crypto/PGPKeyPairTest.kt b/crypto-pgpainless/src/test/kotlin/dev/msfjarvis/aps/crypto/PGPKeyPairTest.kt
new file mode 100644
index 00000000..9fc3ed69
--- /dev/null
+++ b/crypto-pgpainless/src/test/kotlin/dev/msfjarvis/aps/crypto/PGPKeyPairTest.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright © 2014-2021 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+package dev.msfjarvis.aps.crypto
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import org.pgpainless.PGPainless
+
+public class PGPKeyPairTest {
+
+ @Test
+ public fun testIfKeyIdIsCorrect() {
+ val secretKey = PGPainless.readKeyRing().secretKeyRing(getKey()).secretKey
+ val keyPair = PGPKeyPair(secretKey)
+
+ assertEquals(CryptoConstants.KEY_ID, keyPair.getKeyId())
+ }
+
+ private fun getKey(): String = this::class.java.classLoader.getResource("private_key").readText()
+}
diff --git a/crypto-pgpainless/src/test/resources/private_key b/crypto-pgpainless/src/test/resources/private_key
new file mode 100644
index 00000000..61334b01
--- /dev/null
+++ b/crypto-pgpainless/src/test/resources/private_key
@@ -0,0 +1,26 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: PGPainless
+Comment: BC98 82EF 93DC 22F8 D7D4 47AD 08ED F756 7183 CE27
+Comment: John Doe <john.doe@example.com>
+
+lIYEYT33+BYJKwYBBAHaRw8BAQdAoofwCvOfKJ4pGxEO4s64wFD+QnePpNY5zXgW
+TTOFb2/+CQMCh3Bp60ThtX9g8u+uxtuLdeeU5UC14Ox4zVD/x2L7sUzN94XVocOn
+WVJTIgeZ1CBhrsSOMg5grj0Zwf1YODlBpZ85V8stPebpjZ2mCZUz1rQfSm9obiBE
+b2UgPGpvaG4uZG9lQGV4YW1wbGUuY29tPoh4BBMWCgAgBQJhPff4AhsBBRYCAwEA
+BAsJCAcFFQoJCAsCHgECGQEACgkQCO33VnGDzifl1gD8CIAGoF23Yi1aAM8sI0Sq
+33AgyBGmQOsAy1dfLItKRawBAKijCl6cayrl/GG5FxLfDpCz79DDUaqeiJ3GGKhH
+0n4AnIsEYT33+BIKKwYBBAGXVQEFAQEHQLt4VWwVSJ/ir1K1oEjokDCwj6FBICjc
+jpXiNTeuLHxfAwEIB/4JAwKHcGnrROG1f2AcnEUWhC2rDrztJB3JK7pe+PVJbMaK
+O2eYKLiBZOT6Dy1rexMi0vS19IMYLf1V2qgsO9phoglOD+m95tr8Ha9FhfbpJjua
+iHUEGBYKAB0FAmE99/gCGwwFFgIDAQAECwkIBwUVCgkICwIeAQAKCRAI7fdWcYPO
+J5p+AQC5g/FmMU3ayalGVBNU3Bb8xua9P/6zzPFbreV/isFF4wEA1lT9timgPFV6
+Xr0sZEt5/7YtCo0FShBcxm5sAdnU0wmchgRhPff4FgkrBgEEAdpHDwEBB0CV36g4
+wjvS+Kgbutv1D6UOatOt/JBvPgBn/4SR9qtgU/4JAwKHcGnrROG1f2A1hnm2UXZL
+Go/tPJo3pJCJDLClIKi7I5RoHruafuQ2ODvznLbCnbuft9B2cA5MZUMFCk6nBvoU
+k6hwGWxOSNJIOmrCx+PMiNUEGBYKAH0FAmE99/gCGwIFFgIDAQAECwkIBwUVCgkI
+CwIeAV8gBBkWCgAGBQJhPff4AAoJEGSLoii3QC8mrhcBALzpJQTHF8cJJRA9+DQ3
+qZ85Eu217MJix1aYA1i0zyP5AQD/jN/aBsSTqAHF+zU8/ezzHeoilyBYgxLS9Q2q
+elDeDAAKCRAI7fdWcYPOJ7aHAP9EBq0rzV3c6GtVl8bPnk+llpV/1aodxTSnijQt
+VSMuMAD+JMUDJd2bimlhuVwpu0DFiF7IF64SAxmVifTwsTWYiQs=
+=/dDf
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 9ac87b21..8e22f4d2 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -61,7 +61,6 @@ dagger-hilt-core = { module = "com.google.dagger:hilt-core", version.ref = "hilt
android-desugarJdkLibs = "com.android.tools:desugar_jdk_libs:1.1.5"
# First-party libraries
-aps-gopenpgp = "com.github.android-password-store:gopenpgp:0.1.5"
aps-sublimeFuzzy = "com.github.android-password-store:sublime-fuzzy:1.0.0"
aps-zxingAndroidEmbedded = "com.github.android-password-store:zxing-android-embedded:4.2.1"
@@ -76,6 +75,7 @@ thirdparty-kotlinResult = "com.michael-bull.kotlin-result:kotlin-result:1.1.12"
thirdparty-leakcanary = "com.squareup.leakcanary:leakcanary-android:2.7"
thirdparty-logcat = "com.squareup.logcat:logcat:0.1"
thirdparty-modernAndroidPrefs = "de.maxr1998:modernandroidpreferences:2.1.0"
+thirdparty-pgpainless = "org.pgpainless:pgpainless-core:0.2.17"
thirdparty-plumber = "com.squareup.leakcanary:plumber-android:2.7"
thirdparty-sshj = "com.hierynomus:sshj:0.31.0"
thirdparty-sshauth = "com.github.open-keychain.open-keychain:sshauthentication-api:5.7.5"
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 56392b74..c8c641b8 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -10,7 +10,7 @@ include(":autofill-parser")
include(":crypto-common")
-include(":crypto-pgp")
+include(":crypto-pgpainless")
include(":format-common")