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>
This commit is contained in:
parent
21c8653e68
commit
aac74ae451
30 changed files with 353 additions and 402 deletions
62
.github/workflows/pull_request.yml
vendored
62
.github/workflows/pull_request.yml
vendored
|
@ -51,65 +51,3 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: Test report
|
name: Test report
|
||||||
path: app/build/reports
|
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
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ dependencies {
|
||||||
implementation(libs.androidx.annotation)
|
implementation(libs.androidx.annotation)
|
||||||
coreLibraryDesugaring(libs.android.desugarJdkLibs)
|
coreLibraryDesugaring(libs.android.desugarJdkLibs)
|
||||||
implementation(projects.autofillParser)
|
implementation(projects.autofillParser)
|
||||||
implementation(projects.cryptoPgp)
|
implementation(projects.cryptoPgpainless)
|
||||||
implementation(projects.formatCommon)
|
implementation(projects.formatCommon)
|
||||||
implementation(projects.openpgpKtx)
|
implementation(projects.openpgpKtx)
|
||||||
implementation(libs.androidx.activity.ktx)
|
implementation(libs.androidx.activity.ktx)
|
||||||
|
|
|
@ -10,8 +10,8 @@ import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import dagger.multibindings.IntoSet
|
import dagger.multibindings.IntoSet
|
||||||
import dev.msfjarvis.aps.data.crypto.CryptoHandler
|
import dev.msfjarvis.aps.crypto.CryptoHandler
|
||||||
import dev.msfjarvis.aps.data.crypto.GopenpgpCryptoHandler
|
import dev.msfjarvis.aps.crypto.PGPainlessCryptoHandler
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This module adds all [CryptoHandler] implementations into a Set which makes it easier to build
|
* This module adds all [CryptoHandler] implementations into a Set which makes it easier to build
|
||||||
|
@ -23,7 +23,7 @@ object CryptoHandlerModule {
|
||||||
@Provides
|
@Provides
|
||||||
@IntoSet
|
@IntoSet
|
||||||
fun providePgpCryptoHandler(): CryptoHandler {
|
fun providePgpCryptoHandler(): CryptoHandler {
|
||||||
return GopenpgpCryptoHandler()
|
return PGPainlessCryptoHandler()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ import dev.msfjarvis.aps.util.autofill.AutofillPreferences
|
||||||
import dev.msfjarvis.aps.util.autofill.AutofillResponseBuilder
|
import dev.msfjarvis.aps.util.autofill.AutofillResponseBuilder
|
||||||
import dev.msfjarvis.aps.util.autofill.DirectoryStructure
|
import dev.msfjarvis.aps.util.autofill.DirectoryStructure
|
||||||
import dev.msfjarvis.aps.util.extensions.asLog
|
import dev.msfjarvis.aps.util.extensions.asLog
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -130,11 +131,14 @@ class AutofillDecryptActivityV2 : AppCompatActivity() {
|
||||||
runCatching {
|
runCatching {
|
||||||
val crypto = cryptos.first { it.canHandle(file.absolutePath) }
|
val crypto = cryptos.first { it.canHandle(file.absolutePath) }
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
val outputStream = ByteArrayOutputStream()
|
||||||
crypto.decrypt(
|
crypto.decrypt(
|
||||||
DecryptActivityV2.PRIV_KEY,
|
DecryptActivityV2.PRIV_KEY,
|
||||||
DecryptActivityV2.PASS.toByteArray(charset = Charsets.UTF_8),
|
DecryptActivityV2.PASS,
|
||||||
encryptedInput.readBytes()
|
encryptedInput,
|
||||||
|
outputStream,
|
||||||
)
|
)
|
||||||
|
outputStream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onFailure { e ->
|
.onFailure { e ->
|
||||||
|
@ -143,7 +147,7 @@ class AutofillDecryptActivityV2 : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
.onSuccess { result ->
|
.onSuccess { result ->
|
||||||
return runCatching {
|
return runCatching {
|
||||||
val entry = passwordEntryFactory.create(lifecycleScope, result)
|
val entry = passwordEntryFactory.create(lifecycleScope, result.toByteArray())
|
||||||
AutofillPreferences.credentialsFromStoreEntry(this, file, entry, directoryStructure)
|
AutofillPreferences.credentialsFromStoreEntry(this, file, entry, directoryStructure)
|
||||||
}
|
}
|
||||||
.getOrElse { e ->
|
.getOrElse { e ->
|
||||||
|
|
|
@ -20,6 +20,7 @@ import dev.msfjarvis.aps.injection.password.PasswordEntryFactory
|
||||||
import dev.msfjarvis.aps.ui.adapters.FieldItemAdapter
|
import dev.msfjarvis.aps.ui.adapters.FieldItemAdapter
|
||||||
import dev.msfjarvis.aps.util.extensions.unsafeLazy
|
import dev.msfjarvis.aps.util.extensions.unsafeLazy
|
||||||
import dev.msfjarvis.aps.util.extensions.viewBinding
|
import dev.msfjarvis.aps.util.extensions.viewBinding
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
@ -126,19 +127,22 @@ class DecryptActivityV2 : BasePgpActivity() {
|
||||||
private fun decrypt() {
|
private fun decrypt() {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
// TODO(msfjarvis): native methods are fallible, add error handling once out of testing
|
// 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 =
|
val result =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val crypto = cryptos.first { it.canHandle(fullPath) }
|
val crypto = cryptos.first { it.canHandle(fullPath) }
|
||||||
|
val outputStream = ByteArrayOutputStream()
|
||||||
crypto.decrypt(
|
crypto.decrypt(
|
||||||
PRIV_KEY,
|
PRIV_KEY,
|
||||||
PASS.toByteArray(charset = Charsets.UTF_8),
|
PASS,
|
||||||
message,
|
message,
|
||||||
|
outputStream,
|
||||||
)
|
)
|
||||||
|
outputStream
|
||||||
}
|
}
|
||||||
startAutoDismissTimer()
|
startAutoDismissTimer()
|
||||||
|
|
||||||
val entry = passwordEntryFactory.create(lifecycleScope, result)
|
val entry = passwordEntryFactory.create(lifecycleScope, result.toByteArray())
|
||||||
passwordEntry = entry
|
passwordEntry = entry
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ import dev.msfjarvis.aps.util.extensions.snackbar
|
||||||
import dev.msfjarvis.aps.util.extensions.unsafeLazy
|
import dev.msfjarvis.aps.util.extensions.unsafeLazy
|
||||||
import dev.msfjarvis.aps.util.extensions.viewBinding
|
import dev.msfjarvis.aps.util.extensions.viewBinding
|
||||||
import dev.msfjarvis.aps.util.settings.PreferenceKeys
|
import dev.msfjarvis.aps.util.settings.PreferenceKeys
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -319,7 +320,15 @@ class PasswordCreationActivityV2 : BasePgpActivity() {
|
||||||
runCatching {
|
runCatching {
|
||||||
val crypto = cryptos.first { it.canHandle(path) }
|
val crypto = cryptos.first { it.canHandle(path) }
|
||||||
val result =
|
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)
|
val file = File(path)
|
||||||
// If we're not editing, this file should not already exist!
|
// If we're not editing, this file should not already exist!
|
||||||
// Additionally, if we were editing and the incoming and outgoing
|
// Additionally, if we were editing and the incoming and outgoing
|
||||||
|
@ -336,7 +345,7 @@ class PasswordCreationActivityV2 : BasePgpActivity() {
|
||||||
return@runCatching
|
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
|
// associate the new password name with the last name's timestamp in
|
||||||
// history
|
// history
|
||||||
|
|
|
@ -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;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
public synthetic fun <init> (Ljava/lang/String;Lkotlin/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 canHandle (Ljava/lang/String;)Z
|
||||||
public abstract fun decrypt (Ljava/lang/String;[B[B)[B
|
public abstract fun decrypt (Ljava/lang/String;Ljava/lang/String;Ljava/io/InputStream;Ljava/io/OutputStream;)V
|
||||||
public abstract fun encrypt (Ljava/lang/String;[B)[B
|
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 interface class dev/msfjarvis/aps/crypto/KeyManager {
|
||||||
public abstract fun addKey (Ldev/msfjarvis/aps/data/crypto/KeyPair;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
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 canHandle (Ljava/lang/String;)Z
|
||||||
public abstract fun getAllKeys (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
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 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 final class dev/msfjarvis/aps/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 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;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
public synthetic fun <init> (Ljava/lang/String;Lkotlin/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 fun <init> (Ljava/lang/String;)V
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class dev/msfjarvis/aps/data/crypto/KeyManagerException$KeyDeletionFailedException : dev/msfjarvis/aps/data/crypto/KeyManagerException {
|
public final class dev/msfjarvis/aps/crypto/KeyManagerException$KeyDeletionFailedException : dev/msfjarvis/aps/crypto/KeyManagerException {
|
||||||
public static final field INSTANCE Ldev/msfjarvis/aps/data/crypto/KeyManagerException$KeyDeletionFailedException;
|
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 final class dev/msfjarvis/aps/crypto/KeyManagerException$KeyDirectoryUnavailableException : dev/msfjarvis/aps/crypto/KeyManagerException {
|
||||||
public static final field INSTANCE Ldev/msfjarvis/aps/data/crypto/KeyManagerException$KeyDirectoryUnavailableException;
|
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 fun <init> (Ljava/lang/String;)V
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class dev/msfjarvis/aps/data/crypto/KeyManagerException$NoKeysAvailableException : dev/msfjarvis/aps/data/crypto/KeyManagerException {
|
public final class dev/msfjarvis/aps/crypto/KeyManagerException$NoKeysAvailableException : dev/msfjarvis/aps/crypto/KeyManagerException {
|
||||||
public static final field INSTANCE Ldev/msfjarvis/aps/data/crypto/KeyManagerException$NoKeysAvailableException;
|
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 getKeyId ()Ljava/lang/String;
|
||||||
public abstract fun getPrivateKey ()[B
|
public abstract fun getPrivateKey ()[B
|
||||||
public abstract fun getPublicKey ()[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;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
public synthetic fun <init> (Ljava/lang/String;Lkotlin/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 final class dev/msfjarvis/aps/crypto/KeyPairException$PrivateKeyUnavailableException : dev/msfjarvis/aps/crypto/KeyPairException {
|
||||||
public static final field INSTANCE Ldev/msfjarvis/aps/data/crypto/KeyPairException$PrivateKeyUnavailableException;
|
public static final field INSTANCE Ldev/msfjarvis/aps/crypto/KeyPairException$PrivateKeyUnavailableException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package dev.msfjarvis.aps.data.crypto
|
package dev.msfjarvis.aps.crypto
|
||||||
|
|
||||||
public sealed class CryptoException(message: String? = null) : Exception(message)
|
public sealed class CryptoException(message: String? = null) : Exception(message)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.msfjarvis.aps.data.crypto
|
package dev.msfjarvis.aps.crypto
|
||||||
|
|
||||||
import com.github.michaelbull.result.Result
|
import com.github.michaelbull.result.Result
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
* 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. */
|
/** Defines expectations for a keypair used in public key cryptography. */
|
||||||
public interface KeyPair {
|
public interface KeyPair {
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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-----
|
|
|
@ -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>
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
31
crypto-pgpainless/api/crypto-pgpainless.api
Normal file
31
crypto-pgpainless/api/crypto-pgpainless.api
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
|
20
crypto-pgpainless/build.gradle.kts
Normal file
20
crypto-pgpainless/build.gradle.kts
Normal file
|
@ -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)
|
||||||
|
}
|
|
@ -3,22 +3,24 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.msfjarvis.aps.data.crypto
|
package dev.msfjarvis.aps.crypto
|
||||||
|
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import com.github.michaelbull.result.Result
|
import com.github.michaelbull.result.Result
|
||||||
import com.github.michaelbull.result.runCatching
|
import com.github.michaelbull.result.runCatching
|
||||||
import com.proton.Gopenpgp.crypto.Crypto
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.pgpainless.PGPainless
|
||||||
|
|
||||||
public class GPGKeyManager(filesDir: String, private val dispatcher: CoroutineDispatcher) :
|
public class PGPKeyManager(
|
||||||
KeyManager<GPGKeyPair> {
|
filesDir: String,
|
||||||
|
private val dispatcher: CoroutineDispatcher,
|
||||||
|
) : KeyManager<PGPKeyPair> {
|
||||||
|
|
||||||
private val keyDir = File(filesDir, KEY_DIR_NAME)
|
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) {
|
withContext(dispatcher) {
|
||||||
runCatching {
|
runCatching {
|
||||||
if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
|
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) {
|
withContext(dispatcher) {
|
||||||
runCatching {
|
runCatching {
|
||||||
if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
|
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) {
|
withContext(dispatcher) {
|
||||||
runCatching {
|
runCatching {
|
||||||
if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
|
if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
|
||||||
|
@ -56,7 +58,9 @@ public class GPGKeyManager(filesDir: String, private val dispatcher: CoroutineDi
|
||||||
if (keys.isNullOrEmpty()) throw KeyManagerException.NoKeysAvailableException
|
if (keys.isNullOrEmpty()) throw KeyManagerException.NoKeysAvailableException
|
||||||
|
|
||||||
for (keyFile in keys) {
|
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
|
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) {
|
withContext(dispatcher) {
|
||||||
runCatching {
|
runCatching {
|
||||||
if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
|
if (!keyDirExists()) throw KeyManagerException.KeyDirectoryUnavailableException
|
||||||
val keys = keyDir.listFiles()
|
val keys = keyDir.listFiles()
|
||||||
if (keys.isNullOrEmpty()) return@runCatching listOf()
|
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()
|
return keyDir.exists() || keyDir.mkdirs()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal companion object {
|
public companion object {
|
||||||
|
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||||
internal const val KEY_DIR_NAME: String = "keys"
|
internal const val KEY_DIR_NAME: String = "keys"
|
||||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||||
internal const val KEY_EXTENSION: String = "key"
|
internal const val KEY_EXTENSION: String = "key"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
public fun makeKey(armoredKey: String): PGPKeyPair {
|
||||||
|
val secretKey = PGPainless.readKeyRing().secretKeyRing(armoredKey).secretKey
|
||||||
|
return PGPKeyPair(secretKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,12 +3,12 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package dev.msfjarvis.aps.crypto.utils
|
package dev.msfjarvis.aps.crypto
|
||||||
|
|
||||||
internal object CryptoConstants {
|
internal object CryptoConstants {
|
||||||
internal const val KEY_PASSPHRASE = "hunter2"
|
internal const val KEY_PASSPHRASE = "hunter2"
|
||||||
internal const val PLAIN_TEXT = "encryption worthy content"
|
internal const val PLAIN_TEXT = "encryption worthy content"
|
||||||
internal const val KEY_NAME = "John Doe"
|
internal const val KEY_NAME = "John Doe"
|
||||||
internal const val KEY_EMAIL = "john.doe@example.com"
|
internal const val KEY_EMAIL = "john.doe@example.com"
|
||||||
internal const val KEY_ID = "04ace699d5b15b7e"
|
internal const val KEY_ID = "08edf7567183ce27"
|
||||||
}
|
}
|
|
@ -1,59 +1,44 @@
|
||||||
package dev.msfjarvis.aps.crypto
|
package dev.msfjarvis.aps.crypto
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
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 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 java.io.File
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertIs
|
import kotlin.test.assertIs
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.TestCoroutineDispatcher
|
import kotlinx.coroutines.test.TestCoroutineDispatcher
|
||||||
import kotlinx.coroutines.test.runBlockingTest
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
import org.junit.After
|
import org.junit.Rule
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TemporaryFolder
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
public class GPGKeyManagerTest {
|
public class PGPKeyManagerTest {
|
||||||
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
private val testCoroutineDispatcher = TestCoroutineDispatcher()
|
private val testCoroutineDispatcher = TestCoroutineDispatcher()
|
||||||
private lateinit var gpgKeyManager: GPGKeyManager
|
private val keyManager by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
private lateinit var key: GPGKeyPair
|
PGPKeyManager(filesDir.absolutePath, testCoroutineDispatcher)
|
||||||
|
|
||||||
@Before
|
|
||||||
public fun setup() {
|
|
||||||
gpgKeyManager = GPGKeyManager(getFilesDir().absolutePath, testCoroutineDispatcher)
|
|
||||||
key = GPGKeyPair(Key(getKey()))
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public fun tearDown() {
|
|
||||||
val filesDir = getFilesDir()
|
|
||||||
val keysDir = File(filesDir, GPGKeyManager.KEY_DIR_NAME)
|
|
||||||
|
|
||||||
keysDir.deleteRecursively()
|
|
||||||
}
|
}
|
||||||
|
private val key = PGPKeyManager.makeKey(getArmoredKey())
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public fun testAddingKey() {
|
public fun testAddingKey() {
|
||||||
runBlockingTest {
|
runBlockingTest {
|
||||||
// Check if the key id returned is correct
|
// 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)
|
assertEquals(CryptoConstants.KEY_ID, keyId)
|
||||||
|
|
||||||
// Check if the keys directory have one file
|
// Check if the keys directory have one file
|
||||||
val keysDir = File(getFilesDir(), GPGKeyManager.KEY_DIR_NAME)
|
assertEquals(1, filesDir.list()?.size)
|
||||||
assertEquals(1, keysDir.list()?.size)
|
|
||||||
|
|
||||||
// Check if the file name is correct
|
// Check if the file name is correct
|
||||||
val keyFile = keysDir.listFiles()?.first()
|
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() {
|
public fun testAddingKeyWithoutReplaceFlag() {
|
||||||
runBlockingTest {
|
runBlockingTest {
|
||||||
// Check adding the keys twice
|
// Check adding the keys twice
|
||||||
gpgKeyManager.addKey(key, false).unwrap()
|
keyManager.addKey(key, false).unwrap()
|
||||||
val error = gpgKeyManager.addKey(key, false).unwrapError()
|
val error = keyManager.addKey(key, false).unwrapError()
|
||||||
|
|
||||||
assertIs<KeyManagerException.KeyAlreadyExistsException>(error)
|
assertIs<KeyManagerException.KeyAlreadyExistsException>(error)
|
||||||
}
|
}
|
||||||
|
@ -72,8 +57,8 @@ public class GPGKeyManagerTest {
|
||||||
public fun testAddingKeyWithReplaceFlag() {
|
public fun testAddingKeyWithReplaceFlag() {
|
||||||
runBlockingTest {
|
runBlockingTest {
|
||||||
// Check adding the keys twice
|
// Check adding the keys twice
|
||||||
gpgKeyManager.addKey(key, true).unwrap()
|
keyManager.addKey(key, true).unwrap()
|
||||||
val keyId = gpgKeyManager.addKey(key, true).unwrap().getKeyId()
|
val keyId = keyManager.addKey(key, true).unwrap().getKeyId()
|
||||||
|
|
||||||
assertEquals(CryptoConstants.KEY_ID, keyId)
|
assertEquals(CryptoConstants.KEY_ID, keyId)
|
||||||
}
|
}
|
||||||
|
@ -83,14 +68,14 @@ public class GPGKeyManagerTest {
|
||||||
public fun testRemovingKey() {
|
public fun testRemovingKey() {
|
||||||
runBlockingTest {
|
runBlockingTest {
|
||||||
// Add key using KeyManager
|
// Add key using KeyManager
|
||||||
gpgKeyManager.addKey(key).unwrap()
|
keyManager.addKey(key).unwrap()
|
||||||
|
|
||||||
// Check if the key id returned is correct
|
// 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)
|
assertEquals(CryptoConstants.KEY_ID, keyId)
|
||||||
|
|
||||||
// Check if the keys directory have 0 files
|
// 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)
|
assertEquals(0, keysDir.list()?.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,10 +84,10 @@ public class GPGKeyManagerTest {
|
||||||
public fun testGetExistingKey() {
|
public fun testGetExistingKey() {
|
||||||
runBlockingTest {
|
runBlockingTest {
|
||||||
// Add key using KeyManager
|
// 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
|
// 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(CryptoConstants.KEY_ID, key.getKeyId())
|
||||||
assertEquals(key.getKeyId(), returnedKeyPair.getKeyId())
|
assertEquals(key.getKeyId(), returnedKeyPair.getKeyId())
|
||||||
}
|
}
|
||||||
|
@ -112,12 +97,12 @@ public class GPGKeyManagerTest {
|
||||||
public fun testGetNonExistentKey() {
|
public fun testGetNonExistentKey() {
|
||||||
runBlockingTest {
|
runBlockingTest {
|
||||||
// Add key using KeyManager
|
// Add key using KeyManager
|
||||||
gpgKeyManager.addKey(key).unwrap()
|
keyManager.addKey(key).unwrap()
|
||||||
|
|
||||||
val randomKeyId = "0x123456789"
|
val randomKeyId = "0x123456789"
|
||||||
|
|
||||||
// Check returned key
|
// Check returned key
|
||||||
val error = gpgKeyManager.getKeyById(randomKeyId).unwrapError()
|
val error = keyManager.getKeyById(randomKeyId).unwrapError()
|
||||||
assertIs<KeyManagerException.KeyNotFoundException>(error)
|
assertIs<KeyManagerException.KeyNotFoundException>(error)
|
||||||
assertEquals("No key found with id: $randomKeyId", error.message)
|
assertEquals("No key found with id: $randomKeyId", error.message)
|
||||||
}
|
}
|
||||||
|
@ -127,7 +112,7 @@ public class GPGKeyManagerTest {
|
||||||
public fun testFindKeysWithoutAdding() {
|
public fun testFindKeysWithoutAdding() {
|
||||||
runBlockingTest {
|
runBlockingTest {
|
||||||
// Check returned key
|
// Check returned key
|
||||||
val error = gpgKeyManager.getKeyById("0x123456789").unwrapError()
|
val error = keyManager.getKeyById("0x123456789").unwrapError()
|
||||||
assertIs<KeyManagerException.NoKeysAvailableException>(error)
|
assertIs<KeyManagerException.NoKeysAvailableException>(error)
|
||||||
assertEquals("No keys were found", error.message)
|
assertEquals("No keys were found", error.message)
|
||||||
}
|
}
|
||||||
|
@ -138,28 +123,17 @@ public class GPGKeyManagerTest {
|
||||||
runBlockingTest {
|
runBlockingTest {
|
||||||
// TODO: Should we check for more than 1 keys?
|
// TODO: Should we check for more than 1 keys?
|
||||||
// Check if KeyManager returns no key
|
// Check if KeyManager returns no key
|
||||||
val noKeyList = gpgKeyManager.getAllKeys().unwrap()
|
val noKeyList = keyManager.getAllKeys().unwrap()
|
||||||
assertEquals(0, noKeyList.size)
|
assertEquals(0, noKeyList.size)
|
||||||
|
|
||||||
// Add key using KeyManager
|
// Add key using KeyManager
|
||||||
gpgKeyManager.addKey(key).unwrap()
|
keyManager.addKey(key).unwrap()
|
||||||
|
|
||||||
// Check if KeyManager returns one key
|
// Check if KeyManager returns one key
|
||||||
val singleKeyList = gpgKeyManager.getAllKeys().unwrap()
|
val singleKeyList = keyManager.getAllKeys().unwrap()
|
||||||
assertEquals(1, singleKeyList.size)
|
assertEquals(1, singleKeyList.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private fun getArmoredKey() = this::class.java.classLoader.getResource("private_key").readText()
|
||||||
|
|
||||||
fun getFilesDir(): File = InstrumentationRegistry.getInstrumentation().context.filesDir
|
|
||||||
|
|
||||||
fun getKey(): String =
|
|
||||||
InstrumentationRegistry.getInstrumentation()
|
|
||||||
.context
|
|
||||||
.resources
|
|
||||||
.openRawResource(R.raw.private_key)
|
|
||||||
.readBytes()
|
|
||||||
.decodeToString()
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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()
|
||||||
|
}
|
26
crypto-pgpainless/src/test/resources/private_key
Normal file
26
crypto-pgpainless/src/test/resources/private_key
Normal file
|
@ -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-----
|
|
@ -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"
|
android-desugarJdkLibs = "com.android.tools:desugar_jdk_libs:1.1.5"
|
||||||
|
|
||||||
# First-party libraries
|
# 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-sublimeFuzzy = "com.github.android-password-store:sublime-fuzzy:1.0.0"
|
||||||
aps-zxingAndroidEmbedded = "com.github.android-password-store:zxing-android-embedded:4.2.1"
|
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-leakcanary = "com.squareup.leakcanary:leakcanary-android:2.7"
|
||||||
thirdparty-logcat = "com.squareup.logcat:logcat:0.1"
|
thirdparty-logcat = "com.squareup.logcat:logcat:0.1"
|
||||||
thirdparty-modernAndroidPrefs = "de.maxr1998:modernandroidpreferences:2.1.0"
|
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-plumber = "com.squareup.leakcanary:plumber-android:2.7"
|
||||||
thirdparty-sshj = "com.hierynomus:sshj:0.31.0"
|
thirdparty-sshj = "com.hierynomus:sshj:0.31.0"
|
||||||
thirdparty-sshauth = "com.github.open-keychain.open-keychain:sshauthentication-api:5.7.5"
|
thirdparty-sshauth = "com.github.open-keychain.open-keychain:sshauthentication-api:5.7.5"
|
||||||
|
|
|
@ -10,7 +10,7 @@ include(":autofill-parser")
|
||||||
|
|
||||||
include(":crypto-common")
|
include(":crypto-common")
|
||||||
|
|
||||||
include(":crypto-pgp")
|
include(":crypto-pgpainless")
|
||||||
|
|
||||||
include(":format-common")
|
include(":format-common")
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue