diff options
author | Harsh Shandilya <me@msfjarvis.dev> | 2020-11-25 03:27:42 +0530 |
---|---|---|
committer | Harsh Shandilya <me@msfjarvis.dev> | 2020-11-29 17:51:16 +0530 |
commit | 7b25bf878315a894122e3de74e19add6fcdf5d4c (patch) | |
tree | ccb89e08265d628f8ff9a3de562ddb4252199dee /openpgp-ktx | |
parent | fa2a93769f5cec8397064337fd53659147ba1877 (diff) |
openpgp-ktx: import at 95eed95fb5654e9dcfe32c3712ae97385e186396
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Diffstat (limited to 'openpgp-ktx')
16 files changed, 1761 insertions, 0 deletions
diff --git a/openpgp-ktx/README.md b/openpgp-ktx/README.md new file mode 100644 index 00000000..2ee2ef00 --- /dev/null +++ b/openpgp-ktx/README.md @@ -0,0 +1,14 @@ +# openpgp-ktx [![Download](https://api.bintray.com/packages/android-password-store/openpgp-ktx/openpgp-ktx/images/download.svg)](https://bintray.com/android-password-store/openpgp-ktx/openpgp-ktx/_latestVersion) + +Reimplementation of [OpenKeychain]'s integration library [openpgp-api]. Written entirely in Kotlin, it leverages Jetpack to be compatible with modern apps, unlike the original library. + +## Using this with your projects + +```gradle +dependencies { + implementation("com.github.androidpasswordstore:openpgp-ktx:<latest-version>") +} +``` + +[OpenKeychain]: https://github.com/open-keychain/open-keychain +[openpgp-api]: https://github.com/open-keychain/openpgp-api diff --git a/openpgp-ktx/api/openpgp-ktx.api b/openpgp-ktx/api/openpgp-ktx.api new file mode 100644 index 00000000..dad53f16 --- /dev/null +++ b/openpgp-ktx/api/openpgp-ktx.api @@ -0,0 +1,272 @@ +public final class me/msfjarvis/openpgpktx/AutocryptPeerUpdate : android/os/Parcelable { + public static final field CREATOR Lme/msfjarvis/openpgpktx/AutocryptPeerUpdate$CREATOR; + public fun <init> ()V + public synthetic fun <init> (Landroid/os/Parcel;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun createAutocryptPeerUpdate ([BLjava/util/Date;)Lme/msfjarvis/openpgpktx/AutocryptPeerUpdate; + public fun describeContents ()I + public final fun getEffectiveDate ()Ljava/util/Date; + public final fun getKeyData ()[B + public final fun getPreferEncrypt ()Lme/msfjarvis/openpgpktx/AutocryptPeerUpdate$PreferEncrypt; + public final fun hasKeyData ()Z + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class me/msfjarvis/openpgpktx/AutocryptPeerUpdate$CREATOR : android/os/Parcelable$Creator { + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public fun createFromParcel (Landroid/os/Parcel;)Lme/msfjarvis/openpgpktx/AutocryptPeerUpdate; + public synthetic fun newArray (I)[Ljava/lang/Object; + public fun newArray (I)[Lme/msfjarvis/openpgpktx/AutocryptPeerUpdate; +} + +public final class me/msfjarvis/openpgpktx/AutocryptPeerUpdate$PreferEncrypt : java/lang/Enum { + public static final field MUTUAL Lme/msfjarvis/openpgpktx/AutocryptPeerUpdate$PreferEncrypt; + public static final field NOPREFERENCE Lme/msfjarvis/openpgpktx/AutocryptPeerUpdate$PreferEncrypt; + public static fun valueOf (Ljava/lang/String;)Lme/msfjarvis/openpgpktx/AutocryptPeerUpdate$PreferEncrypt; + public static fun values ()[Lme/msfjarvis/openpgpktx/AutocryptPeerUpdate$PreferEncrypt; +} + +public final class me/msfjarvis/openpgpktx/util/OpenPgpApi { + public static final field ACTION_BACKUP Ljava/lang/String; + public static final field ACTION_CHECK_PERMISSION Ljava/lang/String; + public static final field ACTION_CLEARTEXT_SIGN Ljava/lang/String; + public static final field ACTION_DECRYPT_METADATA Ljava/lang/String; + public static final field ACTION_DECRYPT_VERIFY Ljava/lang/String; + public static final field ACTION_DETACHED_SIGN Ljava/lang/String; + public static final field ACTION_ENCRYPT Ljava/lang/String; + public static final field ACTION_GET_KEY Ljava/lang/String; + public static final field ACTION_GET_KEY_IDS Ljava/lang/String; + public static final field ACTION_GET_SIGN_KEY_ID Ljava/lang/String; + public static final field ACTION_QUERY_AUTOCRYPT_STATUS Ljava/lang/String; + public static final field ACTION_SIGN_AND_ENCRYPT Ljava/lang/String; + public static final field ACTION_UPDATE_AUTOCRYPT_PEER Ljava/lang/String; + public static final field API_VERSION I + public static final field AUTOCRYPT_STATUS_AVAILABLE I + public static final field AUTOCRYPT_STATUS_DISCOURAGE I + public static final field AUTOCRYPT_STATUS_MUTUAL I + public static final field AUTOCRYPT_STATUS_UNAVAILABLE I + public static final field Companion Lme/msfjarvis/openpgpktx/util/OpenPgpApi$Companion; + public static final field EXTRA_API_VERSION Ljava/lang/String; + public static final field EXTRA_AUTOCRYPT_PEER_GOSSIP_UPDATES Ljava/lang/String; + public static final field EXTRA_AUTOCRYPT_PEER_ID Ljava/lang/String; + public static final field EXTRA_AUTOCRYPT_PEER_UPDATE Ljava/lang/String; + public static final field EXTRA_BACKUP_SECRET Ljava/lang/String; + public static final field EXTRA_DATA_LENGTH Ljava/lang/String; + public static final field EXTRA_DECRYPTION_RESULT Ljava/lang/String; + public static final field EXTRA_DETACHED_SIGNATURE Ljava/lang/String; + public static final field EXTRA_ENABLE_COMPRESSION Ljava/lang/String; + public static final field EXTRA_KEY_ID Ljava/lang/String; + public static final field EXTRA_KEY_IDS Ljava/lang/String; + public static final field EXTRA_KEY_IDS_SELECTED Ljava/lang/String; + public static final field EXTRA_MINIMIZE Ljava/lang/String; + public static final field EXTRA_MINIMIZE_USER_ID Ljava/lang/String; + public static final field EXTRA_OPPORTUNISTIC_ENCRYPTION Ljava/lang/String; + public static final field EXTRA_ORIGINAL_FILENAME Ljava/lang/String; + public static final field EXTRA_PASSPHRASE Ljava/lang/String; + public static final field EXTRA_PROGRESS_MESSENGER Ljava/lang/String; + public static final field EXTRA_REQUEST_ASCII_ARMOR Ljava/lang/String; + public static final field EXTRA_SENDER_ADDRESS Ljava/lang/String; + public static final field EXTRA_SIGN_KEY_ID Ljava/lang/String; + public static final field EXTRA_SUPPORT_OVERRIDE_CRYPTO_WARNING Ljava/lang/String; + public static final field EXTRA_USER_ID Ljava/lang/String; + public static final field EXTRA_USER_IDS Ljava/lang/String; + public static final field RESULT_AUTOCRYPT_STATUS Ljava/lang/String; + public static final field RESULT_CHARSET Ljava/lang/String; + public static final field RESULT_CODE Ljava/lang/String; + public static final field RESULT_CODE_ERROR I + public static final field RESULT_CODE_SUCCESS I + public static final field RESULT_CODE_USER_INTERACTION_REQUIRED I + public static final field RESULT_DECRYPTION Ljava/lang/String; + public static final field RESULT_DETACHED_SIGNATURE Ljava/lang/String; + public static final field RESULT_ERROR Ljava/lang/String; + public static final field RESULT_INSECURE_DETAIL_INTENT Ljava/lang/String; + public static final field RESULT_INTENT Ljava/lang/String; + public static final field RESULT_KEYS_CONFIRMED Ljava/lang/String; + public static final field RESULT_KEY_IDS Ljava/lang/String; + public static final field RESULT_METADATA Ljava/lang/String; + public static final field RESULT_OVERRIDE_CRYPTO_WARNING Ljava/lang/String; + public static final field RESULT_SIGNATURE Ljava/lang/String; + public static final field RESULT_SIGNATURE_MICALG Ljava/lang/String; + public static final field SERVICE_INTENT_2 Ljava/lang/String; + public fun <init> (Landroid/content/Context;Lorg/openintents/openpgp/IOpenPgpService2;)V + public final fun executeApi (Landroid/content/Intent;Ljava/io/InputStream;Ljava/io/OutputStream;)Landroid/content/Intent; + public final fun executeApiAsync (Landroid/content/Intent;Ljava/io/InputStream;Ljava/io/OutputStream;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class me/msfjarvis/openpgpktx/util/OpenPgpApi$Companion { +} + +public final class me/msfjarvis/openpgpktx/util/OpenPgpServiceConnection { + public fun <init> (Landroid/content/Context;Ljava/lang/String;)V + public fun <init> (Landroid/content/Context;Ljava/lang/String;Lme/msfjarvis/openpgpktx/util/OpenPgpServiceConnection$OnBound;)V + public final fun bindToService ()V + public final fun getService ()Lorg/openintents/openpgp/IOpenPgpService2; + public final fun isBound ()Z + public final fun unbindFromService ()V +} + +public abstract interface class me/msfjarvis/openpgpktx/util/OpenPgpServiceConnection$OnBound { + public abstract fun onBound (Lorg/openintents/openpgp/IOpenPgpService2;)V + public abstract fun onError (Ljava/lang/Exception;)V +} + +public final class me/msfjarvis/openpgpktx/util/OpenPgpUtils { + public static final field INSTANCE Lme/msfjarvis/openpgpktx/util/OpenPgpUtils; + public static final field PARSE_RESULT_MESSAGE I + public static final field PARSE_RESULT_NO_PGP I + public static final field PARSE_RESULT_SIGNED_MESSAGE I + public final fun convertKeyIdToHex (J)Ljava/lang/String; + public final fun createUserId (Lme/msfjarvis/openpgpktx/util/OpenPgpUtils$UserId;)Ljava/lang/String; + public final fun isAvailable (Landroid/content/Context;)Z + public final fun parseMessage (Ljava/lang/String;)I + public final fun splitUserId (Ljava/lang/String;)Lme/msfjarvis/openpgpktx/util/OpenPgpUtils$UserId; +} + +public final class me/msfjarvis/openpgpktx/util/OpenPgpUtils$UserId : java/io/Serializable { + public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public final fun getComment ()Ljava/lang/String; + public final fun getEmail ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; +} + +public abstract interface class org/openintents/openpgp/IOpenPgpService2 : android/os/IInterface { + public abstract fun createOutputPipe (I)Landroid/os/ParcelFileDescriptor; + public abstract fun execute (Landroid/content/Intent;Landroid/os/ParcelFileDescriptor;I)Landroid/content/Intent; +} + +public class org/openintents/openpgp/IOpenPgpService2$Default : org/openintents/openpgp/IOpenPgpService2 { + public fun <init> ()V + public fun asBinder ()Landroid/os/IBinder; + public fun createOutputPipe (I)Landroid/os/ParcelFileDescriptor; + public fun execute (Landroid/content/Intent;Landroid/os/ParcelFileDescriptor;I)Landroid/content/Intent; +} + +public abstract class org/openintents/openpgp/IOpenPgpService2$Stub : android/os/Binder, org/openintents/openpgp/IOpenPgpService2 { + public fun <init> ()V + public fun asBinder ()Landroid/os/IBinder; + public static fun asInterface (Landroid/os/IBinder;)Lorg/openintents/openpgp/IOpenPgpService2; + public static fun getDefaultImpl ()Lorg/openintents/openpgp/IOpenPgpService2; + public fun onTransact (ILandroid/os/Parcel;Landroid/os/Parcel;I)Z + public static fun setDefaultImpl (Lorg/openintents/openpgp/IOpenPgpService2;)Z +} + +public final class org/openintents/openpgp/OpenPgpDecryptionResult : android/os/Parcelable { + public static final field CREATOR Lorg/openintents/openpgp/OpenPgpDecryptionResult$CREATOR; + public static final field RESULT_ENCRYPTED I + public static final field RESULT_INSECURE I + public static final field RESULT_NOT_ENCRYPTED I + public fun <init> ()V + public synthetic fun <init> (I[B[BLkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun describeContents ()I + public final fun getDecryptedSessionKey ()[B + public final fun getResult ()I + public final fun getSessionKey ()[B + public final fun hasDecryptedSessionKey ()Z + public fun toString ()Ljava/lang/String; + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class org/openintents/openpgp/OpenPgpDecryptionResult$CREATOR : android/os/Parcelable$Creator { + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public fun createFromParcel (Landroid/os/Parcel;)Lorg/openintents/openpgp/OpenPgpDecryptionResult; + public synthetic fun newArray (I)[Ljava/lang/Object; + public fun newArray (I)[Lorg/openintents/openpgp/OpenPgpDecryptionResult; +} + +public final class org/openintents/openpgp/OpenPgpError : android/os/Parcelable { + public static final field CLIENT_SIDE_ERROR I + public static final field CREATOR Lorg/openintents/openpgp/OpenPgpError$CREATOR; + public static final field GENERIC_ERROR I + public static final field INCOMPATIBLE_API_VERSIONS I + public static final field NO_OR_WRONG_PASSPHRASE I + public static final field NO_USER_IDS I + public static final field OPPORTUNISTIC_MISSING_KEYS I + public fun <init> ()V + public fun describeContents ()I + public final fun getErrorId ()I + public final fun getMessage ()Ljava/lang/String; + public final fun setErrorId (I)V + public final fun setMessage (Ljava/lang/String;)V + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class org/openintents/openpgp/OpenPgpError$CREATOR : android/os/Parcelable$Creator { + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public fun createFromParcel (Landroid/os/Parcel;)Lorg/openintents/openpgp/OpenPgpError; + public synthetic fun newArray (I)[Ljava/lang/Object; + public fun newArray (I)[Lorg/openintents/openpgp/OpenPgpError; +} + +public final class org/openintents/openpgp/OpenPgpMetadata : android/os/Parcelable { + public static final field CREATOR Lorg/openintents/openpgp/OpenPgpMetadata$CREATOR; + public fun <init> ()V + public fun describeContents ()I + public final fun getCharset ()Ljava/lang/String; + public final fun getFilename ()Ljava/lang/String; + public final fun getMimeType ()Ljava/lang/String; + public final fun getModificationTime ()J + public final fun getOriginalSize ()J + public final fun setCharset (Ljava/lang/String;)V + public final fun setFilename (Ljava/lang/String;)V + public final fun setMimeType (Ljava/lang/String;)V + public final fun setModificationTime (J)V + public final fun setOriginalSize (J)V + public fun toString ()Ljava/lang/String; + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class org/openintents/openpgp/OpenPgpMetadata$CREATOR : android/os/Parcelable$Creator { + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public fun createFromParcel (Landroid/os/Parcel;)Lorg/openintents/openpgp/OpenPgpMetadata; + public synthetic fun newArray (I)[Ljava/lang/Object; + public fun newArray (I)[Lorg/openintents/openpgp/OpenPgpMetadata; +} + +public final class org/openintents/openpgp/OpenPgpSignatureResult : android/os/Parcelable { + public static final field CREATOR Lorg/openintents/openpgp/OpenPgpSignatureResult$CREATOR; + public static final field RESULT_INVALID_KEY_EXPIRED I + public static final field RESULT_INVALID_KEY_INSECURE I + public static final field RESULT_INVALID_KEY_REVOKED I + public static final field RESULT_INVALID_SIGNATURE I + public static final field RESULT_KEY_MISSING I + public static final field RESULT_NO_SIGNATURE I + public static final field RESULT_VALID_KEY_CONFIRMED I + public static final field RESULT_VALID_KEY_UNCONFIRMED I + public synthetic fun <init> (ILjava/lang/String;JLjava/util/ArrayList;Ljava/util/ArrayList;Lorg/openintents/openpgp/OpenPgpSignatureResult$SenderStatusResult;Ljava/lang/Boolean;Ljava/util/Date;Lorg/openintents/openpgp/OpenPgpSignatureResult$AutocryptPeerResult;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun <init> (Landroid/os/Parcel;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun describeContents ()I + public final fun getConfirmedUserIds ()Ljava/util/List; + public final fun getUserIds ()Ljava/util/List; + public fun toString ()Ljava/lang/String; + public final fun withAutocryptPeerResult (Lorg/openintents/openpgp/OpenPgpSignatureResult$AutocryptPeerResult;)Lorg/openintents/openpgp/OpenPgpSignatureResult; + public final fun withSignatureOnlyFlag (Z)Lorg/openintents/openpgp/OpenPgpSignatureResult; + public fun writeToParcel (Landroid/os/Parcel;I)V +} + +public final class org/openintents/openpgp/OpenPgpSignatureResult$AutocryptPeerResult : java/lang/Enum { + public static final field MISMATCH Lorg/openintents/openpgp/OpenPgpSignatureResult$AutocryptPeerResult; + public static final field NEW Lorg/openintents/openpgp/OpenPgpSignatureResult$AutocryptPeerResult; + public static final field OK Lorg/openintents/openpgp/OpenPgpSignatureResult$AutocryptPeerResult; + public static fun valueOf (Ljava/lang/String;)Lorg/openintents/openpgp/OpenPgpSignatureResult$AutocryptPeerResult; + public static fun values ()[Lorg/openintents/openpgp/OpenPgpSignatureResult$AutocryptPeerResult; +} + +public final class org/openintents/openpgp/OpenPgpSignatureResult$CREATOR : android/os/Parcelable$Creator { + public synthetic fun createFromParcel (Landroid/os/Parcel;)Ljava/lang/Object; + public fun createFromParcel (Landroid/os/Parcel;)Lorg/openintents/openpgp/OpenPgpSignatureResult; + public final fun createWithInvalidSignature ()Lorg/openintents/openpgp/OpenPgpSignatureResult; + public final fun createWithKeyMissing (JLjava/util/Date;)Lorg/openintents/openpgp/OpenPgpSignatureResult; + public final fun createWithNoSignature ()Lorg/openintents/openpgp/OpenPgpSignatureResult; + public final fun createWithValidSignature (ILjava/lang/String;JLjava/util/ArrayList;Ljava/util/ArrayList;Lorg/openintents/openpgp/OpenPgpSignatureResult$SenderStatusResult;Ljava/util/Date;)Lorg/openintents/openpgp/OpenPgpSignatureResult; + public synthetic fun newArray (I)[Ljava/lang/Object; + public fun newArray (I)[Lorg/openintents/openpgp/OpenPgpSignatureResult; +} + +public final class org/openintents/openpgp/OpenPgpSignatureResult$SenderStatusResult : java/lang/Enum { + public static final field UNKNOWN Lorg/openintents/openpgp/OpenPgpSignatureResult$SenderStatusResult; + public static final field USER_ID_CONFIRMED Lorg/openintents/openpgp/OpenPgpSignatureResult$SenderStatusResult; + public static final field USER_ID_MISSING Lorg/openintents/openpgp/OpenPgpSignatureResult$SenderStatusResult; + public static final field USER_ID_UNCONFIRMED Lorg/openintents/openpgp/OpenPgpSignatureResult$SenderStatusResult; + public static fun valueOf (Ljava/lang/String;)Lorg/openintents/openpgp/OpenPgpSignatureResult$SenderStatusResult; + public static fun values ()[Lorg/openintents/openpgp/OpenPgpSignatureResult$SenderStatusResult; +} + diff --git a/openpgp-ktx/build.gradle.kts b/openpgp-ktx/build.gradle.kts new file mode 100644 index 00000000..59765d6b --- /dev/null +++ b/openpgp-ktx/build.gradle.kts @@ -0,0 +1,28 @@ +plugins { + id("com.android.library") + id("maven-publish") + kotlin("android") + `aps-plugin` +} + +android { + defaultConfig { + consumerProguardFiles("consumer-rules.pro") + } + + buildFeatures.aidl = true + + kotlin { + explicitApi() + } + + kotlinOptions { + freeCompilerArgs = freeCompilerArgs + listOf( + "-Xexplicit-api=strict" + ) + } +} + +dependencies { + implementation(Dependencies.Kotlin.Coroutines.core) +} diff --git a/openpgp-ktx/consumer-proguard-rules.pro b/openpgp-ktx/consumer-proguard-rules.pro new file mode 100644 index 00000000..13420011 --- /dev/null +++ b/openpgp-ktx/consumer-proguard-rules.pro @@ -0,0 +1 @@ +-keep class org.openintents.openpgp.** diff --git a/openpgp-ktx/gradle.properties b/openpgp-ktx/gradle.properties new file mode 100644 index 00000000..b510e282 --- /dev/null +++ b/openpgp-ktx/gradle.properties @@ -0,0 +1,17 @@ +GROUP=com.github.androidpasswordstore +VERSION_NAME=2.2.0 +POM_ARTIFACT_ID=openpgp-ktx +POM_ARTIFACT_DESCRIPTION=Reimplementation of OpenKeychain's integration library in Kotlin + +POM_URL=https://github.com/Android-Password-Store/android-password-store +POM_SCM_URL=https://github.com/Android-Password-Store/android-password-store +POM_SCM_CONNECTION=scm:git:https://github.com/Android-Password-Store/android-password-store.git +POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com:Android-Password-Store/android-password-store + +POM_LICENSE_NAME=LGPL-3.0-only WITH LGPL-3.0-linking-exception +POM_LICENSE_URL=https://www.gnu.org/licenses/lgpl-3.0.txt +POM_LICENSE_DIST=repo + +POM_DEVELOPER_ID=android-password-store +POM_DEVELOPER_NAME=The Android Password Store Authors +POM_DEVELOPER_EMAIL=aps@msfjarvis.dev 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) + } + } +} |