aboutsummaryrefslogtreecommitdiff
path: root/openpgp-ktx/src/main/java/me
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/java/me
parentfa2a93769f5cec8397064337fd53659147ba1877 (diff)
openpgp-ktx: import at 95eed95fb5654e9dcfe32c3712ae97385e186396
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Diffstat (limited to 'openpgp-ktx/src/main/java/me')
-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
5 files changed, 777 insertions, 0 deletions
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
+ }
+ }
+}