aboutsummaryrefslogtreecommitdiff
path: root/openpgp-ktx/src/main
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2020-11-25 03:27:42 +0530
committerHarsh Shandilya <me@msfjarvis.dev>2020-11-29 17:51:16 +0530
commit7b25bf878315a894122e3de74e19add6fcdf5d4c (patch)
treeccb89e08265d628f8ff9a3de562ddb4252199dee /openpgp-ktx/src/main
parentfa2a93769f5cec8397064337fd53659147ba1877 (diff)
openpgp-ktx: import at 95eed95fb5654e9dcfe32c3712ae97385e186396
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Diffstat (limited to 'openpgp-ktx/src/main')
-rw-r--r--openpgp-ktx/src/main/AndroidManifest.xml1
-rw-r--r--openpgp-ktx/src/main/aidl/org/openintents/openpgp/IOpenPgpService2.aidl18
-rw-r--r--openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/AutocryptPeerUpdate.kt109
-rw-r--r--openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpApi.kt402
-rw-r--r--openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpServiceConnection.kt88
-rw-r--r--openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpUtils.kt115
-rw-r--r--openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/ParcelFileDescriptorUtil.kt63
-rw-r--r--openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpDecryptionResult.kt126
-rw-r--r--openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpError.kt91
-rw-r--r--openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpMetadata.kt122
-rw-r--r--openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.kt294
11 files changed, 1429 insertions, 0 deletions
diff --git a/openpgp-ktx/src/main/AndroidManifest.xml b/openpgp-ktx/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..f1864ad2
--- /dev/null
+++ b/openpgp-ktx/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+<manifest package="me.msfjarvis.openpgpktx" />
diff --git a/openpgp-ktx/src/main/aidl/org/openintents/openpgp/IOpenPgpService2.aidl b/openpgp-ktx/src/main/aidl/org/openintents/openpgp/IOpenPgpService2.aidl
new file mode 100644
index 00000000..7e67986d
--- /dev/null
+++ b/openpgp-ktx/src/main/aidl/org/openintents/openpgp/IOpenPgpService2.aidl
@@ -0,0 +1,18 @@
+/*
+ * Copyright © 2019 The Android Password Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-Only
+ */
+package org.openintents.openpgp;
+
+interface IOpenPgpService2 {
+
+ /**
+ * see org.openintents.openpgp.util.OpenPgpApi for documentation
+ */
+ ParcelFileDescriptor createOutputPipe(in int pipeId);
+
+ /**
+ * see org.openintents.openpgp.util.OpenPgpApi for documentation
+ */
+ Intent execute(in Intent data, in ParcelFileDescriptor input, int pipeId);
+}
diff --git a/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/AutocryptPeerUpdate.kt b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/AutocryptPeerUpdate.kt
new file mode 100644
index 00000000..a5f6f2b1
--- /dev/null
+++ b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/AutocryptPeerUpdate.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright © 2019 The Android Password Authors. All Rights Reserved.
+ * SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception
+ */
+package me.msfjarvis.openpgpktx
+
+import android.os.Parcel
+import android.os.Parcelable
+import android.os.Parcelable.Creator
+import java.util.Date
+
+public class AutocryptPeerUpdate() : Parcelable {
+
+ private var keyData: ByteArray? = null
+ private var effectiveDate: Date? = null
+ private lateinit var preferEncrypt: PreferEncrypt
+
+ internal constructor(
+ keyData: ByteArray?,
+ effectiveDate: Date?,
+ preferEncrypt: PreferEncrypt
+ ) : this() {
+ this.keyData = keyData
+ this.effectiveDate = effectiveDate
+ this.preferEncrypt = preferEncrypt
+ }
+
+ private constructor(source: Parcel, version: Int) : this() {
+ keyData = source.createByteArray()
+ effectiveDate = if (source.readInt() != 0) Date(source.readLong()) else null
+ preferEncrypt = PreferEncrypt.values()[source.readInt()]
+ }
+
+ public fun createAutocryptPeerUpdate(
+ keyData: ByteArray?,
+ timestamp: Date?
+ ): AutocryptPeerUpdate {
+ return AutocryptPeerUpdate(keyData, timestamp, PreferEncrypt.NOPREFERENCE)
+ }
+
+ public fun getKeyData(): ByteArray? {
+ return keyData
+ }
+
+ public fun hasKeyData(): Boolean {
+ return keyData != null
+ }
+
+ public fun getEffectiveDate(): Date? {
+ return effectiveDate
+ }
+
+ public fun getPreferEncrypt(): PreferEncrypt? {
+ return preferEncrypt
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ /**
+ * NOTE: When adding fields in the process of updating this API, make sure to bump
+ * [.PARCELABLE_VERSION].
+ */
+ dest.writeInt(PARCELABLE_VERSION)
+ // Inject a placeholder that will store the parcel size from this point on
+ // (not including the size itself).
+ val sizePosition = dest.dataPosition()
+ dest.writeInt(0)
+ val startPosition = dest.dataPosition()
+ // version 1
+ dest.writeByteArray(keyData)
+ if (effectiveDate != null) {
+ dest.writeInt(1)
+ dest.writeLong(effectiveDate!!.time)
+ } else {
+ dest.writeInt(0)
+ }
+ dest.writeInt(preferEncrypt.ordinal)
+ // Go back and write the size
+ val parcelableSize = dest.dataPosition() - startPosition
+ dest.setDataPosition(sizePosition)
+ dest.writeInt(parcelableSize)
+ dest.setDataPosition(startPosition + parcelableSize)
+ }
+
+ public companion object CREATOR : Creator<AutocryptPeerUpdate> {
+
+ private const val PARCELABLE_VERSION = 1
+ override fun createFromParcel(source: Parcel): AutocryptPeerUpdate? {
+ val version = source.readInt() // parcelableVersion
+ val parcelableSize = source.readInt()
+ val startPosition = source.dataPosition()
+ val vr = AutocryptPeerUpdate(source, version)
+ // skip over all fields added in future versions of this parcel
+ source.setDataPosition(startPosition + parcelableSize)
+ return vr
+ }
+
+ override fun newArray(size: Int): Array<AutocryptPeerUpdate?>? {
+ return arrayOfNulls(size)
+ }
+ }
+
+ public enum class PreferEncrypt {
+ NOPREFERENCE, MUTUAL
+ }
+}
diff --git a/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpApi.kt b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpApi.kt
new file mode 100644
index 00000000..02b11001
--- /dev/null
+++ b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpApi.kt
@@ -0,0 +1,402 @@
+/*
+ * Copyright © 2019 The Android Password Authors. All Rights Reserved.
+ * SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception
+ */
+@file:Suppress("Unused")
+
+package me.msfjarvis.openpgpktx.util
+
+import android.content.Context
+import android.content.Intent
+import android.os.ParcelFileDescriptor
+import android.util.Log
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+import java.util.concurrent.atomic.AtomicInteger
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import org.openintents.openpgp.IOpenPgpService2
+import org.openintents.openpgp.OpenPgpError
+
+public class OpenPgpApi(private val context: Context, private val service: IOpenPgpService2) {
+
+ private val pipeIdGen: AtomicInteger = AtomicInteger()
+
+ public suspend fun executeApiAsync(
+ data: Intent?,
+ inputStream: InputStream?,
+ outputStream: OutputStream?,
+ callback: (intent: Intent?) -> Unit
+ ) {
+ val result = executeApi(data, inputStream, outputStream)
+ withContext(Dispatchers.Main) {
+ callback.invoke(result)
+ }
+ }
+
+ public fun executeApi(data: Intent?, inputStream: InputStream?, outputStream: OutputStream?): Intent? {
+ var input: ParcelFileDescriptor? = null
+ return try {
+ if (inputStream != null) {
+ input = ParcelFileDescriptorUtil.pipeFrom(inputStream)
+ }
+ executeApi(data, input, outputStream)
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception in executeApi call", e)
+ val result = Intent()
+ result.putExtra(RESULT_CODE, RESULT_CODE_ERROR)
+ result.putExtra(
+ RESULT_ERROR,
+ OpenPgpError(
+ OpenPgpError.CLIENT_SIDE_ERROR,
+ e.message
+ )
+ )
+ result
+ } finally {
+ if (input != null) {
+ try {
+ input.close()
+ } catch (e: IOException) {
+ Log.e(TAG, "IOException when closing ParcelFileDescriptor!", e)
+ }
+ }
+ }
+ }
+
+ /**
+ * InputStream and OutputStreams are always closed after operating on them!
+ */
+ private fun executeApi(
+ data: Intent?,
+ input: ParcelFileDescriptor?,
+ os: OutputStream?
+ ): Intent? {
+ var output: ParcelFileDescriptor? = null
+ return try {
+ // always send version from client
+ data?.putExtra(EXTRA_API_VERSION, API_VERSION)
+ val result: Intent
+ var pumpThread: Thread? = null
+ var outputPipeId = 0
+ if (os != null) {
+ outputPipeId = pipeIdGen.incrementAndGet()
+ output = service.createOutputPipe(outputPipeId)
+ pumpThread = ParcelFileDescriptorUtil.pipeTo(os, output)
+ }
+ // blocks until result is ready
+ result = service.execute(data, input, outputPipeId)
+ // set class loader to current context to allow unparcelling
+ // of OpenPgpError and OpenPgpSignatureResult
+ // http://stackoverflow.com/a/3806769
+ result.setExtrasClassLoader(context.classLoader)
+ // wait for ALL data being pumped from remote side
+ pumpThread?.join()
+ result
+ } catch (e: Exception) {
+ Log.e(TAG, "Exception in executeApi call", e)
+ val result = Intent()
+ result.putExtra(RESULT_CODE, RESULT_CODE_ERROR)
+ result.putExtra(
+ RESULT_ERROR,
+ OpenPgpError(
+ OpenPgpError.CLIENT_SIDE_ERROR,
+ e.message
+ )
+ )
+ result
+ } finally {
+ // close() is required to halt the TransferThread
+ if (output != null) {
+ try {
+ output.close()
+ } catch (e: IOException) {
+ Log.e(TAG, "IOException when closing ParcelFileDescriptor!", e)
+ }
+ }
+ }
+ }
+
+ public companion object {
+
+ private const val TAG = "OpenPgp API"
+
+ public const val SERVICE_INTENT_2: String = "org.openintents.openpgp.IOpenPgpService2"
+
+ /**
+ * see CHANGELOG.md
+ */
+ public const val API_VERSION: Int = 11
+
+ /**
+ * General extras
+ * --------------
+ *
+ * required extras:
+ * int EXTRA_API_VERSION (always required)
+ *
+ * returned extras:
+ * int RESULT_CODE (RESULT_CODE_ERROR, RESULT_CODE_SUCCESS or RESULT_CODE_USER_INTERACTION_REQUIRED)
+ * OpenPgpError RESULT_ERROR (if RESULT_CODE == RESULT_CODE_ERROR)
+ * PendingIntent RESULT_INTENT (if RESULT_CODE == RESULT_CODE_USER_INTERACTION_REQUIRED)
+ */
+
+ /**
+ * General extras
+ * --------------
+ *
+ * required extras:
+ * int EXTRA_API_VERSION (always required)
+ *
+ * returned extras:
+ * int RESULT_CODE (RESULT_CODE_ERROR, RESULT_CODE_SUCCESS or RESULT_CODE_USER_INTERACTION_REQUIRED)
+ * OpenPgpError RESULT_ERROR (if RESULT_CODE == RESULT_CODE_ERROR)
+ * PendingIntent RESULT_INTENT (if RESULT_CODE == RESULT_CODE_USER_INTERACTION_REQUIRED)
+ */
+ /**
+ * This action performs no operation, but can be used to check if the App has permission
+ * to access the API in general, returning a user interaction PendingIntent otherwise.
+ * This can be used to trigger the permission dialog explicitly.
+ *
+ * This action uses no extras.
+ */
+ public const val ACTION_CHECK_PERMISSION: String = "org.openintents.openpgp.action.CHECK_PERMISSION"
+
+ /**
+ * Sign text resulting in a cleartext signature
+ * Some magic pre-processing of the text is done to convert it to a format usable for
+ * cleartext signatures per RFC 4880 before the text is actually signed:
+ * - end cleartext with newline
+ * - remove whitespaces on line endings
+ *
+ * required extras:
+ * long EXTRA_SIGN_KEY_ID (key id of signing key)
+ *
+ * optional extras:
+ * char[] EXTRA_PASSPHRASE (key passphrase)
+ */
+ public const val ACTION_CLEARTEXT_SIGN: String = "org.openintents.openpgp.action.CLEARTEXT_SIGN"
+
+ /**
+ * Sign text or binary data resulting in a detached signature.
+ * No OutputStream necessary for ACTION_DETACHED_SIGN (No magic pre-processing like in ACTION_CLEARTEXT_SIGN)!
+ * The detached signature is returned separately in RESULT_DETACHED_SIGNATURE.
+ *
+ * required extras:
+ * long EXTRA_SIGN_KEY_ID (key id of signing key)
+ *
+ * optional extras:
+ * boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for detached signature)
+ * char[] EXTRA_PASSPHRASE (key passphrase)
+ *
+ * returned extras:
+ * byte[] RESULT_DETACHED_SIGNATURE
+ * String RESULT_SIGNATURE_MICALG (contains the name of the used signature algorithm as a string)
+ */
+ public const val ACTION_DETACHED_SIGN: String = "org.openintents.openpgp.action.DETACHED_SIGN"
+
+ /**
+ * Encrypt
+ *
+ * required extras:
+ * String[] EXTRA_USER_IDS (=emails of recipients, if more than one key has a user_id, a PendingIntent is returned via RESULT_INTENT)
+ * or
+ * long[] EXTRA_KEY_IDS
+ *
+ * optional extras:
+ * boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for output)
+ * char[] EXTRA_PASSPHRASE (key passphrase)
+ * String EXTRA_ORIGINAL_FILENAME (original filename to be encrypted as metadata)
+ * boolean EXTRA_ENABLE_COMPRESSION (enable ZLIB compression, default ist true)
+ */
+ public const val ACTION_ENCRYPT: String = "org.openintents.openpgp.action.ENCRYPT"
+
+ /**
+ * Sign and encrypt
+ *
+ * required extras:
+ * String[] EXTRA_USER_IDS (=emails of recipients, if more than one key has a user_id, a PendingIntent is returned via RESULT_INTENT)
+ * or
+ * long[] EXTRA_KEY_IDS
+ *
+ * optional extras:
+ * long EXTRA_SIGN_KEY_ID (key id of signing key)
+ * boolean EXTRA_REQUEST_ASCII_ARMOR (request ascii armor for output)
+ * char[] EXTRA_PASSPHRASE (key passphrase)
+ * String EXTRA_ORIGINAL_FILENAME (original filename to be encrypted as metadata)
+ * boolean EXTRA_ENABLE_COMPRESSION (enable ZLIB compression, default ist true)
+ */
+ public const val ACTION_SIGN_AND_ENCRYPT: String = "org.openintents.openpgp.action.SIGN_AND_ENCRYPT"
+
+ public const val ACTION_QUERY_AUTOCRYPT_STATUS: String =
+ "org.openintents.openpgp.action.QUERY_AUTOCRYPT_STATUS"
+
+ /**
+ * Decrypts and verifies given input stream. This methods handles encrypted-only, signed-and-encrypted,
+ * and also signed-only input.
+ * OutputStream is optional, e.g., for verifying detached signatures!
+ *
+ * If OpenPgpSignatureResult.getResult() == OpenPgpSignatureResult.RESULT_KEY_MISSING
+ * in addition a PendingIntent is returned via RESULT_INTENT to download missing keys.
+ * On all other status, in addition a PendingIntent is returned via RESULT_INTENT to open
+ * the key view in OpenKeychain.
+ *
+ * optional extras:
+ * byte[] EXTRA_DETACHED_SIGNATURE (detached signature)
+ *
+ * returned extras:
+ * OpenPgpSignatureResult RESULT_SIGNATURE
+ * OpenPgpDecryptionResult RESULT_DECRYPTION
+ * OpenPgpDecryptMetadata RESULT_METADATA
+ * String RESULT_CHARSET (charset which was specified in the headers of ascii armored input, if any)
+ */
+ public const val ACTION_DECRYPT_VERIFY: String = "org.openintents.openpgp.action.DECRYPT_VERIFY"
+
+ /**
+ * Decrypts the header of an encrypted file to retrieve metadata such as original filename.
+ *
+ * This does not decrypt the actual content of the file.
+ *
+ * returned extras:
+ * OpenPgpDecryptMetadata RESULT_METADATA
+ * String RESULT_CHARSET (charset which was specified in the headers of ascii armored input, if any)
+ */
+ public const val ACTION_DECRYPT_METADATA: String = "org.openintents.openpgp.action.DECRYPT_METADATA"
+
+ /**
+ * Select key id for signing
+ *
+ * optional extras:
+ * String EXTRA_USER_ID
+ *
+ * returned extras:
+ * long EXTRA_SIGN_KEY_ID
+ */
+ public const val ACTION_GET_SIGN_KEY_ID: String = "org.openintents.openpgp.action.GET_SIGN_KEY_ID"
+
+ /**
+ * Get key ids based on given user ids (=emails)
+ *
+ * required extras:
+ * String[] EXTRA_USER_IDS
+ *
+ * returned extras:
+ * long[] RESULT_KEY_IDS
+ */
+ public const val ACTION_GET_KEY_IDS: String = "org.openintents.openpgp.action.GET_KEY_IDS"
+
+ /**
+ * This action returns RESULT_CODE_SUCCESS if the OpenPGP Provider already has the key
+ * corresponding to the given key id in its database.
+ *
+ * It returns RESULT_CODE_USER_INTERACTION_REQUIRED if the Provider does not have the key.
+ * The PendingIntent from RESULT_INTENT can be used to retrieve those from a keyserver.
+ *
+ * If an Output stream has been defined the whole public key is returned.
+ * required extras:
+ * long EXTRA_KEY_ID
+ *
+ * optional extras:
+ * String EXTRA_REQUEST_ASCII_ARMOR (request that the returned key is encoded in ASCII Armor)
+ */
+ public const val ACTION_GET_KEY: String = "org.openintents.openpgp.action.GET_KEY"
+
+ /**
+ * Backup all keys given by EXTRA_KEY_IDS and if requested their secret parts.
+ * The encrypted backup will be written to the OutputStream.
+ * The client app has no access to the backup code used to encrypt the backup!
+ * This operation always requires user interaction with RESULT_CODE_USER_INTERACTION_REQUIRED!
+ *
+ * required extras:
+ * long[] EXTRA_KEY_IDS (keys that should be included in the backup)
+ * boolean EXTRA_BACKUP_SECRET (also backup secret keys)
+ */
+ public const val ACTION_BACKUP: String = "org.openintents.openpgp.action.BACKUP"
+
+ public const val ACTION_UPDATE_AUTOCRYPT_PEER: String =
+ "org.openintents.openpgp.action.UPDATE_AUTOCRYPT_PEER"
+
+ /* Intent extras */
+ public const val EXTRA_API_VERSION: String = "api_version"
+
+ // ACTION_DETACHED_SIGN, ENCRYPT, SIGN_AND_ENCRYPT, DECRYPT_VERIFY
+ // request ASCII Armor for output
+ // OpenPGP Radix-64, 33 percent overhead compared to binary, see http://tools.ietf.org/html/rfc4880#page-53)
+ public const val EXTRA_REQUEST_ASCII_ARMOR: String = "ascii_armor"
+
+ // ACTION_DETACHED_SIGN
+ public const val RESULT_DETACHED_SIGNATURE: String = "detached_signature"
+ public const val RESULT_SIGNATURE_MICALG: String = "signature_micalg"
+
+ // ENCRYPT, SIGN_AND_ENCRYPT, QUERY_AUTOCRYPT_STATUS
+ public const val EXTRA_USER_IDS: String = "user_ids"
+ public const val EXTRA_KEY_IDS: String = "key_ids"
+ public const val EXTRA_KEY_IDS_SELECTED: String = "key_ids_selected"
+ public const val EXTRA_SIGN_KEY_ID: String = "sign_key_id"
+
+ public const val RESULT_KEYS_CONFIRMED: String = "keys_confirmed"
+ public const val RESULT_AUTOCRYPT_STATUS: String = "autocrypt_status"
+ public const val AUTOCRYPT_STATUS_UNAVAILABLE: Int = 0
+ public const val AUTOCRYPT_STATUS_DISCOURAGE: Int = 1
+ public const val AUTOCRYPT_STATUS_AVAILABLE: Int = 2
+ public const val AUTOCRYPT_STATUS_MUTUAL: Int = 3
+
+ // optional extras:
+ public const val EXTRA_PASSPHRASE: String = "passphrase"
+ public const val EXTRA_ORIGINAL_FILENAME: String = "original_filename"
+ public const val EXTRA_ENABLE_COMPRESSION: String = "enable_compression"
+ public const val EXTRA_OPPORTUNISTIC_ENCRYPTION: String = "opportunistic"
+
+ // GET_SIGN_KEY_ID
+ public const val EXTRA_USER_ID: String = "user_id"
+
+ // GET_KEY
+ public const val EXTRA_KEY_ID: String = "key_id"
+ public const val EXTRA_MINIMIZE: String = "minimize"
+ public const val EXTRA_MINIMIZE_USER_ID: String = "minimize_user_id"
+ public const val RESULT_KEY_IDS: String = "key_ids"
+
+ // BACKUP
+ public const val EXTRA_BACKUP_SECRET: String = "backup_secret"
+
+ /* Service Intent returns */
+ public const val RESULT_CODE: String = "result_code"
+
+ // get actual error object from RESULT_ERROR
+ public const val RESULT_CODE_ERROR: Int = 0
+
+ // success!
+ public const val RESULT_CODE_SUCCESS: Int = 1
+
+ // get PendingIntent from RESULT_INTENT, start PendingIntent with startIntentSenderForResult,
+ // and execute service method again in onActivityResult
+ public const val RESULT_CODE_USER_INTERACTION_REQUIRED: Int = 2
+
+ public const val RESULT_ERROR: String = "error"
+ public const val RESULT_INTENT: String = "intent"
+
+ // DECRYPT_VERIFY
+ public const val EXTRA_DETACHED_SIGNATURE: String = "detached_signature"
+ public const val EXTRA_PROGRESS_MESSENGER: String = "progress_messenger"
+ public const val EXTRA_DATA_LENGTH: String = "data_length"
+ public const val EXTRA_DECRYPTION_RESULT: String = "decryption_result"
+ public const val EXTRA_SENDER_ADDRESS: String = "sender_address"
+ public const val EXTRA_SUPPORT_OVERRIDE_CRYPTO_WARNING: String = "support_override_crpto_warning"
+ public const val EXTRA_AUTOCRYPT_PEER_ID: String = "autocrypt_peer_id"
+ public const val EXTRA_AUTOCRYPT_PEER_UPDATE: String = "autocrypt_peer_update"
+ public const val EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES: String = "autocrypt_peer_gossip_updates"
+ public const val RESULT_SIGNATURE: String = "signature"
+ public const val RESULT_DECRYPTION: String = "decryption"
+ public const val RESULT_METADATA: String = "metadata"
+ public const val RESULT_INSECURE_DETAIL_INTENT: String = "insecure_detail_intent"
+ public const val RESULT_OVERRIDE_CRYPTO_WARNING: String = "override_crypto_warning"
+
+ // This will be the charset which was specified in the headers of ascii armored input, if any
+ public const val RESULT_CHARSET: String = "charset"
+
+ // INTERNAL, must not be used
+ internal const val EXTRA_CALL_UUID1 = "call_uuid1"
+ internal const val EXTRA_CALL_UUID2 = "call_uuid2"
+ }
+}
diff --git a/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpServiceConnection.kt b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpServiceConnection.kt
new file mode 100644
index 00000000..38465f8c
--- /dev/null
+++ b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpServiceConnection.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright © 2019 The Android Password Authors. All Rights Reserved.
+ * SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception
+ */
+package me.msfjarvis.openpgpktx.util
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+import org.openintents.openpgp.IOpenPgpService2
+
+public class OpenPgpServiceConnection(context: Context, providerPackageName: String?) {
+
+ // callback interface
+ public interface OnBound {
+
+ public fun onBound(service: IOpenPgpService2)
+ public fun onError(e: Exception)
+ }
+
+ private val mApplicationContext: Context = context.applicationContext
+ public var service: IOpenPgpService2? = null
+ private set
+ private val mProviderPackageName: String? = providerPackageName
+ private var mOnBoundListener: OnBound? = null
+
+ /**
+ * Create new connection with callback
+ *
+ * @param context
+ * @param providerPackageName specify package name of OpenPGP provider,
+ * e.g., "org.sufficientlysecure.keychain"
+ * @param onBoundListener callback, executed when connection to service has been established
+ */
+ public constructor(
+ context: Context,
+ providerPackageName: String?,
+ onBoundListener: OnBound?
+ ) : this(context, providerPackageName) {
+ mOnBoundListener = onBoundListener
+ }
+
+ public val isBound: Boolean
+ get() = service != null
+
+ private val mServiceConnection: ServiceConnection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName, service: IBinder) {
+ this@OpenPgpServiceConnection.service = IOpenPgpService2.Stub.asInterface(service)
+ mOnBoundListener?.onBound(this@OpenPgpServiceConnection.service!!)
+ }
+
+ override fun onServiceDisconnected(name: ComponentName) {
+ service = null
+ }
+ }
+
+ /**
+ * If not already bound, bind to service!
+ */
+ public fun bindToService() {
+ if (service == null) {
+ // if not already bound...
+ try {
+ val serviceIntent = Intent(OpenPgpApi.SERVICE_INTENT_2)
+ // NOTE: setPackage is very important to restrict the intent to this provider only!
+ serviceIntent.setPackage(mProviderPackageName)
+ val connect = mApplicationContext.bindService(
+ serviceIntent, mServiceConnection,
+ Context.BIND_AUTO_CREATE
+ )
+ if (!connect) {
+ throw Exception("bindService() returned false!")
+ }
+ } catch (e: Exception) {
+ mOnBoundListener?.onError(e)
+ }
+ } else {
+ // already bound, but also inform client about it with callback
+ mOnBoundListener?.onBound(service!!)
+ }
+ }
+
+ public fun unbindFromService() {
+ mApplicationContext.unbindService(mServiceConnection)
+ }
+}
diff --git a/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpUtils.kt b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpUtils.kt
new file mode 100644
index 00000000..cca90653
--- /dev/null
+++ b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/OpenPgpUtils.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright © 2019 The Android Password Authors. All Rights Reserved.
+ * SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception
+ */
+package me.msfjarvis.openpgpktx.util
+
+import android.content.Context
+import android.content.Intent
+import android.text.TextUtils
+import java.io.Serializable
+import java.util.Locale
+import java.util.regex.Pattern
+
+public object OpenPgpUtils {
+
+ private val PGP_MESSAGE: Pattern = Pattern.compile(
+ ".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*",
+ Pattern.DOTALL
+ )
+ private val PGP_SIGNED_MESSAGE: Pattern = Pattern.compile(
+ ".*?(-----BEGIN PGP SIGNED MESSAGE-----.*?-----BEGIN PGP SIGNATURE-----.*?-----END PGP SIGNATURE-----).*",
+ Pattern.DOTALL
+ )
+ private val USER_ID_PATTERN = Pattern.compile("^(.*?)(?: \\((.*)\\))?(?: <(.*)>)?$")
+ private val EMAIL_PATTERN = Pattern.compile("^<?\"?([^<>\"]*@[^<>\"]*\\.[^<>\"]*)\"?>?$")
+ public const val PARSE_RESULT_NO_PGP: Int = -1
+ public const val PARSE_RESULT_MESSAGE: Int = 0
+ public const val PARSE_RESULT_SIGNED_MESSAGE: Int = 1
+
+ public fun parseMessage(message: String): Int {
+ val matcherSigned = PGP_SIGNED_MESSAGE.matcher(message)
+ val matcherMessage = PGP_MESSAGE.matcher(message)
+ return when {
+ matcherMessage.matches() -> PARSE_RESULT_MESSAGE
+ matcherSigned.matches() -> PARSE_RESULT_SIGNED_MESSAGE
+ else -> PARSE_RESULT_NO_PGP
+ }
+ }
+
+ public fun isAvailable(context: Context): Boolean {
+ val intent = Intent(OpenPgpApi.SERVICE_INTENT_2)
+ val resInfo =
+ context.packageManager.queryIntentServices(intent, 0)
+ return resInfo.isNotEmpty()
+ }
+
+ public fun convertKeyIdToHex(keyId: Long): String {
+ return "0x" + convertKeyIdToHex32bit(keyId shr 32) + convertKeyIdToHex32bit(
+ keyId
+ )
+ }
+
+ private fun convertKeyIdToHex32bit(keyId: Long): String {
+ var hexString =
+ java.lang.Long.toHexString(keyId and 0xffffffffL).toLowerCase(Locale.ENGLISH)
+ while (hexString.length < 8) {
+ hexString = "0$hexString"
+ }
+ return hexString
+ }
+
+ /**
+ * Splits userId string into naming part, email part, and comment part.
+ * See SplitUserIdTest for examples.
+ */
+ public fun splitUserId(userId: String): UserId {
+ if (!TextUtils.isEmpty(userId)) {
+ val matcher = USER_ID_PATTERN.matcher(userId)
+ if (matcher.matches()) {
+ var name = if (matcher.group(1).isEmpty()) null else matcher.group(1)
+ val comment = matcher.group(2)
+ var email = matcher.group(3)
+ if (email != null && name != null) {
+ val emailMatcher = EMAIL_PATTERN.matcher(name)
+ if (emailMatcher.matches() && email == emailMatcher.group(1)) {
+ email = emailMatcher.group(1)
+ name = null
+ }
+ }
+ if (email == null && name != null) {
+ val emailMatcher = EMAIL_PATTERN.matcher(name)
+ if (emailMatcher.matches()) {
+ email = emailMatcher.group(1)
+ name = null
+ }
+ }
+ return UserId(name, email, comment)
+ }
+ }
+ return UserId(null, null, null)
+ }
+
+ /**
+ * Returns a composed user id. Returns null if name, email and comment are empty.
+ */
+ public fun createUserId(userId: UserId): String? {
+ val userIdBuilder = StringBuilder()
+ if (!TextUtils.isEmpty(userId.name)) {
+ userIdBuilder.append(userId.name)
+ }
+ if (!TextUtils.isEmpty(userId.comment)) {
+ userIdBuilder.append(" (")
+ userIdBuilder.append(userId.comment)
+ userIdBuilder.append(")")
+ }
+ if (!TextUtils.isEmpty(userId.email)) {
+ userIdBuilder.append(" <")
+ userIdBuilder.append(userId.email)
+ userIdBuilder.append(">")
+ }
+ return if (userIdBuilder.isEmpty()) null else userIdBuilder.toString()
+ }
+
+ public class UserId(public val name: String?, public val email: String?, public val comment: String?) : Serializable
+}
diff --git a/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/ParcelFileDescriptorUtil.kt b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/ParcelFileDescriptorUtil.kt
new file mode 100644
index 00000000..6f1f9eda
--- /dev/null
+++ b/openpgp-ktx/src/main/java/me/msfjarvis/openpgpktx/util/ParcelFileDescriptorUtil.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright © 2019 The Android Password Authors. All Rights Reserved.
+ * SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception
+ */
+package me.msfjarvis.openpgpktx.util
+
+import android.os.ParcelFileDescriptor
+import android.os.ParcelFileDescriptor.AutoCloseInputStream
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream
+import android.util.Log
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+
+internal object ParcelFileDescriptorUtil {
+
+ private const val TAG = "PFDUtils"
+
+ @Throws(IOException::class)
+ internal fun pipeFrom(inputStream: InputStream): ParcelFileDescriptor {
+ val pipe = ParcelFileDescriptor.createPipe()
+ val readSide = pipe[0]
+ val writeSide = pipe[1]
+ TransferThread(inputStream, AutoCloseOutputStream(writeSide))
+ .start()
+ return readSide
+ }
+
+ @Throws(IOException::class)
+ internal fun pipeTo(outputStream: OutputStream, output: ParcelFileDescriptor?): TransferThread {
+ val t = TransferThread(AutoCloseInputStream(output), outputStream)
+ t.start()
+ return t
+ }
+
+ internal class TransferThread(val `in`: InputStream, private val out: OutputStream) : Thread("IPC Transfer Thread") {
+
+ override fun run() {
+ val buf = ByteArray(4096)
+ var len: Int
+ try {
+ while (`in`.read(buf).also { len = it } > 0) {
+ out.write(buf, 0, len)
+ }
+ } catch (e: IOException) {
+ Log.e(TAG, "IOException when writing to out", e)
+ } finally {
+ try {
+ `in`.close()
+ } catch (ignored: IOException) {
+ }
+ try {
+ out.close()
+ } catch (ignored: IOException) {
+ }
+ }
+ }
+
+ init {
+ isDaemon = true
+ }
+ }
+}
diff --git a/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpDecryptionResult.kt b/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpDecryptionResult.kt
new file mode 100644
index 00000000..0e294ba1
--- /dev/null
+++ b/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpDecryptionResult.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright © 2019 The Android Password Authors. All Rights Reserved.
+ * SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception
+ */
+@file:JvmName("OpenPgpDecryptionResult")
+
+package org.openintents.openpgp
+
+import android.os.Parcel
+import android.os.Parcelable
+import android.os.Parcelable.Creator
+
+public class OpenPgpDecryptionResult() : Parcelable {
+
+ private var result = 0
+ private var sessionKey: ByteArray? = null
+ private var decryptedSessionKey: ByteArray? = null
+
+ private constructor(result: Int) : this() {
+ this.result = result
+ sessionKey = null
+ decryptedSessionKey = null
+ }
+
+ private constructor(
+ result: Int,
+ sessionKey: ByteArray?,
+ decryptedSessionKey: ByteArray?
+ ) : this() {
+ this.result = result
+ if (sessionKey == null != (decryptedSessionKey == null)) {
+ throw AssertionError("sessionkey must be null iff decryptedSessionKey is null")
+ }
+ this.sessionKey = sessionKey
+ this.decryptedSessionKey = decryptedSessionKey
+ }
+
+ public fun getResult(): Int {
+ return result
+ }
+
+ public fun hasDecryptedSessionKey(): Boolean {
+ return sessionKey != null
+ }
+
+ public fun getSessionKey(): ByteArray? {
+ return if (sessionKey == null) {
+ null
+ } else sessionKey!!.copyOf(sessionKey!!.size)
+ }
+
+ public fun getDecryptedSessionKey(): ByteArray? {
+ return if (sessionKey == null || decryptedSessionKey == null) {
+ null
+ } else decryptedSessionKey!!.copyOf(decryptedSessionKey!!.size)
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ /**
+ * NOTE: When adding fields in the process of updating this API, make sure to bump
+ * [.PARCELABLE_VERSION].
+ */
+ dest.writeInt(PARCELABLE_VERSION)
+ // Inject a placeholder that will store the parcel size from this point on
+ // (not including the size itself).
+ val sizePosition = dest.dataPosition()
+ dest.writeInt(0)
+ val startPosition = dest.dataPosition()
+ // version 1
+ dest.writeInt(result)
+ // version 2
+ dest.writeByteArray(sessionKey)
+ dest.writeByteArray(decryptedSessionKey)
+ // Go back and write the size
+ val parcelableSize = dest.dataPosition() - startPosition
+ dest.setDataPosition(sizePosition)
+ dest.writeInt(parcelableSize)
+ dest.setDataPosition(startPosition + parcelableSize)
+ }
+
+ override fun toString(): String {
+ return "\nresult: $result"
+ }
+
+ public companion object CREATOR : Creator<OpenPgpDecryptionResult> {
+
+ /**
+ * Since there might be a case where new versions of the client using the library getting
+ * old versions of the protocol (and thus old versions of this class), we need a versioning
+ * system for the parcels sent between the clients and the providers.
+ */
+ private const val PARCELABLE_VERSION = 2
+
+ // content not encrypted
+ public const val RESULT_NOT_ENCRYPTED: Int = -1
+
+ // insecure!
+ public const val RESULT_INSECURE: Int = 0
+
+ // encrypted
+ public const val RESULT_ENCRYPTED: Int = 1
+
+ override fun createFromParcel(source: Parcel): OpenPgpDecryptionResult? {
+ val version = source.readInt() // parcelableVersion
+ val parcelableSize = source.readInt()
+ val startPosition = source.dataPosition()
+ val result = source.readInt()
+ val sessionKey = if (version > 1) source.createByteArray() else null
+ val decryptedSessionKey =
+ if (version > 1) source.createByteArray() else null
+ val vr =
+ OpenPgpDecryptionResult(result, sessionKey, decryptedSessionKey)
+ // skip over all fields added in future versions of this parcel
+ source.setDataPosition(startPosition + parcelableSize)
+ return vr
+ }
+
+ override fun newArray(size: Int): Array<OpenPgpDecryptionResult?> {
+ return arrayOfNulls(size)
+ }
+ }
+}
diff --git a/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpError.kt b/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpError.kt
new file mode 100644
index 00000000..7cef34aa
--- /dev/null
+++ b/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpError.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright © 2019 The Android Password Authors. All Rights Reserved.
+ * SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception
+ */
+@file:JvmName("OpenPgpError")
+
+package org.openintents.openpgp
+
+import android.os.Parcel
+import android.os.Parcelable
+import android.os.Parcelable.Creator
+
+public class OpenPgpError() : Parcelable {
+
+ public var errorId: Int = 0
+ public var message: String? = null
+
+ private constructor(parcel: Parcel) : this() {
+ errorId = parcel.readInt()
+ message = parcel.readString()
+ }
+
+ internal constructor(errorId: Int, message: String?) : this() {
+ this.errorId = errorId
+ this.message = message
+ }
+
+ internal constructor(b: OpenPgpError) : this() {
+ errorId = b.errorId
+ message = b.message
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ /**
+ * NOTE: When adding fields in the process of updating this API, make sure to bump
+ * [PARCELABLE_VERSION].
+ */
+ dest.writeInt(PARCELABLE_VERSION)
+ // Inject a placeholder that will store the parcel size from this point on
+ // (not including the size itself).
+ val sizePosition = dest.dataPosition()
+ dest.writeInt(0)
+ val startPosition = dest.dataPosition()
+ // version 1
+ dest.writeInt(errorId)
+ dest.writeString(message)
+ // Go back and write the size
+ val parcelableSize = dest.dataPosition() - startPosition
+ dest.setDataPosition(sizePosition)
+ dest.writeInt(parcelableSize)
+ dest.setDataPosition(startPosition + parcelableSize)
+ }
+
+ public companion object CREATOR : Creator<OpenPgpError> {
+
+ /**
+ * Since there might be a case where new versions of the client using the library getting
+ * old versions of the protocol (and thus old versions of this class), we need a versioning
+ * system for the parcels sent between the clients and the providers.
+ */
+ private const val PARCELABLE_VERSION = 1
+
+ // possible values for errorId
+ public const val CLIENT_SIDE_ERROR: Int = -1
+ public const val GENERIC_ERROR: Int = 0
+ public const val INCOMPATIBLE_API_VERSIONS: Int = 1
+ public const val NO_OR_WRONG_PASSPHRASE: Int = 2
+ public const val NO_USER_IDS: Int = 3
+ public const val OPPORTUNISTIC_MISSING_KEYS: Int = 4
+
+ override fun createFromParcel(source: Parcel): OpenPgpError? {
+ source.readInt() // parcelableVersion
+ val parcelableSize = source.readInt()
+ val startPosition = source.dataPosition()
+ val error = OpenPgpError()
+ error.errorId = source.readInt()
+ error.message = source.readString()
+ // skip over all fields added in future versions of this parcel
+ source.setDataPosition(startPosition + parcelableSize)
+ return error
+ }
+
+ override fun newArray(size: Int): Array<OpenPgpError?> {
+ return arrayOfNulls(size)
+ }
+ }
+}
diff --git a/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpMetadata.kt b/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpMetadata.kt
new file mode 100644
index 00000000..65497e1f
--- /dev/null
+++ b/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpMetadata.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright © 2019 The Android Password Authors. All Rights Reserved.
+ * SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception
+ */
+@file:JvmName("OpenPgpMetadata")
+
+package org.openintents.openpgp
+
+import android.os.Parcel
+import android.os.Parcelable
+import android.os.Parcelable.Creator
+
+public class OpenPgpMetadata() : Parcelable {
+
+ public var filename: String? = null
+ public var mimeType: String? = null
+ public var charset: String? = null
+ public var modificationTime: Long = 0
+ public var originalSize: Long = 0
+
+ private constructor(
+ filename: String?,
+ mimeType: String?,
+ modificationTime: Long,
+ originalSize: Long,
+ charset: String?
+ ) : this() {
+ this.filename = filename
+ this.mimeType = mimeType
+ this.modificationTime = modificationTime
+ this.originalSize = originalSize
+ this.charset = charset
+ }
+
+ private constructor(
+ filename: String?,
+ mimeType: String?,
+ modificationTime: Long,
+ originalSize: Long
+ ) : this() {
+ this.filename = filename
+ this.mimeType = mimeType
+ this.modificationTime = modificationTime
+ this.originalSize = originalSize
+ }
+
+ private constructor(b: OpenPgpMetadata) : this() {
+ filename = b.filename
+ mimeType = b.mimeType
+ modificationTime = b.modificationTime
+ originalSize = b.originalSize
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ /**
+ * NOTE: When adding fields in the process of updating this API, make sure to bump
+ * [PARCELABLE_VERSION].
+ */
+ dest.writeInt(PARCELABLE_VERSION)
+ // Inject a placeholder that will store the parcel size from this point on
+ // (not including the size itself).
+ val sizePosition = dest.dataPosition()
+ dest.writeInt(0)
+ val startPosition = dest.dataPosition()
+ // version 1
+ dest.writeString(filename)
+ dest.writeString(mimeType)
+ dest.writeLong(modificationTime)
+ dest.writeLong(originalSize)
+ // version 2
+ dest.writeString(charset)
+ // Go back and write the size
+ val parcelableSize = dest.dataPosition() - startPosition
+ dest.setDataPosition(sizePosition)
+ dest.writeInt(parcelableSize)
+ dest.setDataPosition(startPosition + parcelableSize)
+ }
+
+ public companion object CREATOR : Creator<OpenPgpMetadata> {
+
+ /**
+ * Since there might be a case where new versions of the client using the library getting
+ * old versions of the protocol (and thus old versions of this class), we need a versioning
+ * system for the parcels sent between the clients and the providers.
+ */
+ private const val PARCELABLE_VERSION = 2
+
+ override fun createFromParcel(source: Parcel): OpenPgpMetadata? {
+ val version = source.readInt() // parcelableVersion
+ val parcelableSize = source.readInt()
+ val startPosition = source.dataPosition()
+ val vr = OpenPgpMetadata()
+ vr.filename = source.readString()
+ vr.mimeType = source.readString()
+ vr.modificationTime = source.readLong()
+ vr.originalSize = source.readLong()
+ if (version >= 2) {
+ vr.charset = source.readString()
+ }
+ // skip over all fields added in future versions of this parcel
+ source.setDataPosition(startPosition + parcelableSize)
+ return vr
+ }
+
+ override fun newArray(size: Int): Array<OpenPgpMetadata?> {
+ return arrayOfNulls(size)
+ }
+ }
+
+ override fun toString(): String {
+ var out = "\nfilename: $filename"
+ out += "\nmimeType: $mimeType"
+ out += "\nmodificationTime: $modificationTime"
+ out += "\noriginalSize: $originalSize"
+ out += "\ncharset: $charset"
+ return out
+ }
+}
diff --git a/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.kt b/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.kt
new file mode 100644
index 00000000..e318ebe8
--- /dev/null
+++ b/openpgp-ktx/src/main/java/org/openintents/openpgp/OpenPgpSignatureResult.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright © 2019 The Android Password Authors. All Rights Reserved.
+ * SPDX-License-Identifier: LGPL-3.0-only WITH LGPL-3.0-linking-exception
+ */
+@file:JvmName("OpenPgpSignatureResult")
+
+package org.openintents.openpgp
+
+import android.os.Parcel
+import android.os.Parcelable
+import android.os.Parcelable.Creator
+import java.util.Date
+import me.msfjarvis.openpgpktx.util.OpenPgpUtils
+
+public class OpenPgpSignatureResult : Parcelable {
+
+ private val result: Int
+ private val keyId: Long
+ private val primaryUserId: String?
+ private val userIds: ArrayList<String>?
+ private val confirmedUserIds: ArrayList<String>?
+ private val senderStatusResult: SenderStatusResult?
+ private val signatureTimestamp: Date?
+ private val autocryptPeerentityResult: AutocryptPeerResult?
+
+ private constructor(
+ signatureStatus: Int,
+ signatureUserId: String?,
+ keyId: Long,
+ userIds: ArrayList<String>?,
+ confirmedUserIds: ArrayList<String>?,
+ senderStatusResult: SenderStatusResult?,
+ signatureOnly: Boolean?,
+ signatureTimestamp: Date?,
+ autocryptPeerentityResult: AutocryptPeerResult?
+ ) {
+ result = signatureStatus
+ primaryUserId = signatureUserId
+ this.keyId = keyId
+ this.userIds = userIds
+ this.confirmedUserIds = confirmedUserIds
+ this.senderStatusResult = senderStatusResult
+ this.signatureTimestamp = signatureTimestamp
+ this.autocryptPeerentityResult = autocryptPeerentityResult
+ }
+
+ private constructor(source: Parcel, version: Int) {
+ result = source.readInt()
+ // we dropped support for signatureOnly, but need to skip the value for compatibility
+ source.readByte()
+ primaryUserId = source.readString()
+ keyId = source.readLong()
+ userIds = if (version > 1) {
+ source.createStringArrayList()
+ } else {
+ null
+ }
+ // backward compatibility for this exact version
+ if (version > 2) {
+ senderStatusResult = readEnumWithNullAndFallback(
+ source,
+ SenderStatusResult.values(),
+ SenderStatusResult.UNKNOWN
+ )
+ confirmedUserIds = source.createStringArrayList()
+ } else {
+ senderStatusResult = SenderStatusResult.UNKNOWN
+ confirmedUserIds = null
+ }
+ signatureTimestamp = if (version > 3) {
+ if (source.readInt() > 0) Date(source.readLong()) else null
+ } else {
+ null
+ }
+ autocryptPeerentityResult = if (version > 4) {
+ readEnumWithNullAndFallback(
+ source,
+ AutocryptPeerResult.values(),
+ null
+ )
+ } else {
+ null
+ }
+ }
+
+ public fun getUserIds(): List<String> {
+ return (userIds ?: arrayListOf()).toList()
+ }
+
+ public fun getConfirmedUserIds(): List<String> {
+ return (confirmedUserIds ?: arrayListOf()).toList()
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ override fun writeToParcel(dest: Parcel, flags: Int) {
+ /**
+ * NOTE: When adding fields in the process of updating this API, make sure to bump
+ * [.PARCELABLE_VERSION].
+ */
+ dest.writeInt(PARCELABLE_VERSION)
+ // Inject a placeholder that will store the parcel size from this point on
+ // (not including the size itself).
+ val sizePosition = dest.dataPosition()
+ dest.writeInt(0)
+ val startPosition = dest.dataPosition()
+ // version 1
+ dest.writeInt(result)
+ // signatureOnly is deprecated since version 3. we pass a dummy value for compatibility
+ dest.writeByte(0.toByte())
+ dest.writeString(primaryUserId)
+ dest.writeLong(keyId)
+ // version 2
+ dest.writeStringList(userIds)
+ // version 3
+ writeEnumWithNull(dest, senderStatusResult)
+ dest.writeStringList(confirmedUserIds)
+ // version 4
+ if (signatureTimestamp != null) {
+ dest.writeInt(1)
+ dest.writeLong(signatureTimestamp.time)
+ } else {
+ dest.writeInt(0)
+ }
+ // version 5
+ writeEnumWithNull(dest, autocryptPeerentityResult)
+ // Go back and write the size
+ val parcelableSize = dest.dataPosition() - startPosition
+ dest.setDataPosition(sizePosition)
+ dest.writeInt(parcelableSize)
+ dest.setDataPosition(startPosition + parcelableSize)
+ }
+
+ override fun toString(): String {
+ var out = "\nresult: $result"
+ out += "\nprimaryUserId: $primaryUserId"
+ out += "\nuserIds: $userIds"
+ out += "\nkeyId: " + OpenPgpUtils.convertKeyIdToHex(keyId)
+ return out
+ }
+
+ @Deprecated("")
+ public fun withSignatureOnlyFlag(signatureOnly: Boolean): OpenPgpSignatureResult {
+ return OpenPgpSignatureResult(
+ result, primaryUserId, keyId, userIds, confirmedUserIds,
+ senderStatusResult, signatureOnly, signatureTimestamp, autocryptPeerentityResult
+ )
+ }
+
+ public fun withAutocryptPeerResult(autocryptPeerentityResult: AutocryptPeerResult?): OpenPgpSignatureResult {
+ return OpenPgpSignatureResult(
+ result, primaryUserId, keyId, userIds, confirmedUserIds,
+ senderStatusResult, null, signatureTimestamp, autocryptPeerentityResult
+ )
+ }
+
+ public enum class SenderStatusResult {
+ UNKNOWN, USER_ID_CONFIRMED, USER_ID_UNCONFIRMED, USER_ID_MISSING;
+ }
+
+ public enum class AutocryptPeerResult {
+ OK, NEW, MISMATCH;
+ }
+
+ public companion object CREATOR : Creator<OpenPgpSignatureResult> {
+
+ /**
+ * Since there might be a case where new versions of the client using the library getting
+ * old versions of the protocol (and thus old versions of this class), we need a versioning
+ * system for the parcels sent between the clients and the providers.
+ */
+ private const val PARCELABLE_VERSION = 5
+
+ // content not signed
+ public const val RESULT_NO_SIGNATURE: Int = -1
+
+ // invalid signature!
+ public const val RESULT_INVALID_SIGNATURE: Int = 0
+
+ // successfully verified signature, with confirmed key
+ public const val RESULT_VALID_KEY_CONFIRMED: Int = 1
+
+ // no key was found for this signature verification
+ public const val RESULT_KEY_MISSING: Int = 2
+
+ // successfully verified signature, but with unconfirmed key
+ public const val RESULT_VALID_KEY_UNCONFIRMED: Int = 3
+
+ // key has been revoked -> invalid signature!
+ public const val RESULT_INVALID_KEY_REVOKED: Int = 4
+
+ // key is expired -> invalid signature!
+ public const val RESULT_INVALID_KEY_EXPIRED: Int = 5
+
+ // insecure cryptographic algorithms/protocol -> invalid signature!
+ public const val RESULT_INVALID_KEY_INSECURE: Int = 6
+
+ override fun createFromParcel(source: Parcel): OpenPgpSignatureResult? {
+ val version = source.readInt() // parcelableVersion
+ val parcelableSize = source.readInt()
+ val startPosition = source.dataPosition()
+ val vr = OpenPgpSignatureResult(source, version)
+ // skip over all fields added in future versions of this parcel
+ source.setDataPosition(startPosition + parcelableSize)
+ return vr
+ }
+
+ override fun newArray(size: Int): Array<OpenPgpSignatureResult?> {
+ return arrayOfNulls(size)
+ }
+
+ public fun createWithValidSignature(
+ signatureStatus: Int,
+ primaryUserId: String?,
+ keyId: Long,
+ userIds: ArrayList<String>?,
+ confirmedUserIds: ArrayList<String>?,
+ senderStatusResult: SenderStatusResult?,
+ signatureTimestamp: Date?
+ ): OpenPgpSignatureResult {
+ require(!(signatureStatus == RESULT_NO_SIGNATURE || signatureStatus == RESULT_KEY_MISSING || signatureStatus == RESULT_INVALID_SIGNATURE)) { "can only use this method for valid types of signatures" }
+ return OpenPgpSignatureResult(
+ signatureStatus, primaryUserId, keyId, userIds, confirmedUserIds,
+ senderStatusResult, null, signatureTimestamp, null
+ )
+ }
+
+ public fun createWithNoSignature(): OpenPgpSignatureResult {
+ return OpenPgpSignatureResult(
+ RESULT_NO_SIGNATURE,
+ null,
+ 0L,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ )
+ }
+
+ public fun createWithKeyMissing(keyId: Long, signatureTimestamp: Date?): OpenPgpSignatureResult {
+ return OpenPgpSignatureResult(
+ RESULT_KEY_MISSING,
+ null,
+ keyId,
+ null,
+ null,
+ null,
+ null,
+ signatureTimestamp,
+ null
+ )
+ }
+
+ public fun createWithInvalidSignature(): OpenPgpSignatureResult {
+ return OpenPgpSignatureResult(
+ RESULT_INVALID_SIGNATURE,
+ null,
+ 0L,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null
+ )
+ }
+
+ private fun <T : Enum<T>?> readEnumWithNullAndFallback(
+ source: Parcel,
+ enumValues: Array<T>,
+ fallback: T?
+ ): T? {
+ val valueOrdinal = source.readInt()
+ if (valueOrdinal == -1) {
+ return null
+ }
+ return if (valueOrdinal >= enumValues.size) {
+ fallback
+ } else enumValues[valueOrdinal]
+ }
+
+ private fun writeEnumWithNull(dest: Parcel, enumValue: Enum<*>?) {
+ if (enumValue == null) {
+ dest.writeInt(-1)
+ return
+ }
+ dest.writeInt(enumValue.ordinal)
+ }
+ }
+}