From 6acc2a17bb00007c7f40de05b935e01338e7298f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 28 Jun 2017 04:16:41 +0200 Subject: [PATCH 01/11] update OpenPgpApi to version 12 --- .../fsck/k9/message/PgpMessageBuilder.java | 2 +- .../k9/message/PgpMessageBuilderTest.java | 4 +- .../openpgp/AutocryptPeerUpdate.java | 131 ++++++++++++++++++ .../openintents/openpgp/util/OpenPgpApi.java | 117 ++++++++++------ 4 files changed, 210 insertions(+), 44 deletions(-) create mode 100644 plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java diff --git a/k9mail/src/main/java/com/fsck/k9/message/PgpMessageBuilder.java b/k9mail/src/main/java/com/fsck/k9/message/PgpMessageBuilder.java index 46366b0b0..8f4254a5a 100644 --- a/k9mail/src/main/java/com/fsck/k9/message/PgpMessageBuilder.java +++ b/k9mail/src/main/java/com/fsck/k9/message/PgpMessageBuilder.java @@ -154,7 +154,7 @@ public class PgpMessageBuilder extends MessageBuilder { throw new MessagingException("encryption is enabled, but no recipient specified!"); } pgpApiIntent.putExtra(OpenPgpApi.EXTRA_USER_IDS, encryptRecipientAddresses); - pgpApiIntent.putExtra(OpenPgpApi.EXTRA_ENCRYPT_OPPORTUNISTIC, cryptoStatus.isEncryptionOpportunistic()); + pgpApiIntent.putExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, cryptoStatus.isEncryptionOpportunistic()); } } else { pgpApiIntent = new Intent(isPgpInlineMode ? OpenPgpApi.ACTION_SIGN : OpenPgpApi.ACTION_DETACHED_SIGN); diff --git a/k9mail/src/test/java/com/fsck/k9/message/PgpMessageBuilderTest.java b/k9mail/src/test/java/com/fsck/k9/message/PgpMessageBuilderTest.java index 6f84ea661..751081f3b 100644 --- a/k9mail/src/test/java/com/fsck/k9/message/PgpMessageBuilderTest.java +++ b/k9mail/src/test/java/com/fsck/k9/message/PgpMessageBuilderTest.java @@ -277,7 +277,7 @@ public class PgpMessageBuilderTest { expectedApiIntent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, TEST_SIGN_KEY_ID); expectedApiIntent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, new long[] { TEST_SELF_ENCRYPT_KEY_ID }); expectedApiIntent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - expectedApiIntent.putExtra(OpenPgpApi.EXTRA_ENCRYPT_OPPORTUNISTIC, false); + expectedApiIntent.putExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, false); expectedApiIntent.putExtra(OpenPgpApi.EXTRA_USER_IDS, cryptoStatus.getRecipientAddresses()); assertIntentEqualsActionAndExtras(expectedApiIntent, capturedApiIntent.getValue()); @@ -330,7 +330,7 @@ public class PgpMessageBuilderTest { expectedApiIntent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, TEST_SIGN_KEY_ID); expectedApiIntent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, new long[] { TEST_SELF_ENCRYPT_KEY_ID }); expectedApiIntent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); - expectedApiIntent.putExtra(OpenPgpApi.EXTRA_ENCRYPT_OPPORTUNISTIC, false); + expectedApiIntent.putExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, false); expectedApiIntent.putExtra(OpenPgpApi.EXTRA_USER_IDS, cryptoStatus.getRecipientAddresses()); assertIntentEqualsActionAndExtras(expectedApiIntent, capturedApiIntent.getValue()); diff --git a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java new file mode 100644 index 000000000..5b723547d --- /dev/null +++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2014-2015 Dominik Schürmann + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openintents.openpgp; + + +import java.util.Date; + +import android.os.Parcel; +import android.os.Parcelable; + + +@SuppressWarnings("unused") +public class AutocryptPeerUpdate implements Parcelable { + /** + * 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 static final int PARCELABLE_VERSION = 1; + + + private final byte[] keyData; + private final Date effectiveDate; + private final PreferEncrypt preferEncrypt; + + + private AutocryptPeerUpdate(byte[] keyData, Date effectiveDate, PreferEncrypt preferEncrypt) { + this.keyData = keyData; + this.effectiveDate = effectiveDate; + this.preferEncrypt = preferEncrypt; + } + + private AutocryptPeerUpdate(Parcel source, int version) { + this.keyData = source.createByteArray(); + this.effectiveDate = source.readInt() != 0 ? new Date(source.readLong()) : null; + this.preferEncrypt = PreferEncrypt.values()[source.readInt()]; + } + + + public static AutocryptPeerUpdate createAutocryptPeerUpdate(byte[] keyData, Date timestamp) { + return new AutocryptPeerUpdate(keyData, timestamp, PreferEncrypt.NOPREFERENCE); + } + + public byte[] getKeyData() { + return keyData; + } + + public boolean hasKeyData() { + return keyData != null; + } + + public Date getEffectiveDate() { + return effectiveDate; + } + + public PreferEncrypt getPreferEncrypt() { + return preferEncrypt; + } + + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + /** + * NOTE: When adding fields in the process of updating this API, make sure to bump + * {@link #PARCELABLE_VERSION}. + */ + dest.writeInt(PARCELABLE_VERSION); + // Inject a placeholder that will store the parcel size from this point on + // (not including the size itself). + int sizePosition = dest.dataPosition(); + dest.writeInt(0); + int startPosition = dest.dataPosition(); + + // version 1 + dest.writeByteArray(keyData); + if (effectiveDate != null) { + dest.writeInt(1); + dest.writeLong(effectiveDate.getTime()); + } else { + dest.writeInt(0); + } + + dest.writeInt(preferEncrypt.ordinal()); + + // Go back and write the size + int parcelableSize = dest.dataPosition() - startPosition; + dest.setDataPosition(sizePosition); + dest.writeInt(parcelableSize); + dest.setDataPosition(startPosition + parcelableSize); + } + + public static final Creator CREATOR = new Creator() { + public AutocryptPeerUpdate createFromParcel(final Parcel source) { + int version = source.readInt(); // parcelableVersion + int parcelableSize = source.readInt(); + int startPosition = source.dataPosition(); + + AutocryptPeerUpdate vr = new AutocryptPeerUpdate(source, version); + + // skip over all fields added in future versions of this parcel + source.setDataPosition(startPosition + parcelableSize); + + return vr; + } + + public AutocryptPeerUpdate[] newArray(final int size) { + return new AutocryptPeerUpdate[size]; + } + }; + + public enum PreferEncrypt { + NOPREFERENCE, MUTUAL; + } +} diff --git a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpApi.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpApi.java index f8da1c807..15ba659bd 100644 --- a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpApi.java +++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/util/OpenPgpApi.java @@ -38,7 +38,6 @@ import org.openintents.openpgp.util.ParcelFileDescriptorUtil.DataSinkTransferThr import org.openintents.openpgp.util.ParcelFileDescriptorUtil.DataSourceTransferThread; -@SuppressWarnings("unused") public class OpenPgpApi { public static final String TAG = "OpenPgp API"; @@ -48,7 +47,7 @@ public class OpenPgpApi { /** * see CHANGELOG.md */ - public static final int API_VERSION = 10; + public static final int API_VERSION = 12; /** * General extras @@ -63,16 +62,16 @@ public class OpenPgpApi { * 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 static final String ACTION_CHECK_PERMISSION = "org.openintents.openpgp.action.CHECK_PERMISSION"; - /** - * DEPRECATED - * Same as ACTION_CLEARTEXT_SIGN - *

- * optional extras: - * boolean EXTRA_REQUEST_ASCII_ARMOR (DEPRECATED: this makes no sense here) - * char[] EXTRA_PASSPHRASE (key passphrase) - */ + @Deprecated public static final String ACTION_SIGN = "org.openintents.openpgp.action.SIGN"; /** @@ -81,10 +80,10 @@ public class OpenPgpApi { * 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) */ @@ -94,49 +93,50 @@ public class OpenPgpApi { * 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 static final String ACTION_DETACHED_SIGN = "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 is true) + * boolean EXTRA_ENABLE_COMPRESSION (enable ZLIB compression, default ist true) */ public static final String ACTION_ENCRYPT = "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 is true) + * boolean EXTRA_ENABLE_COMPRESSION (enable ZLIB compression, default ist true) */ public static final String ACTION_SIGN_AND_ENCRYPT = "org.openintents.openpgp.action.SIGN_AND_ENCRYPT"; @@ -144,15 +144,15 @@ public class OpenPgpApi { * 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 @@ -163,9 +163,9 @@ public class OpenPgpApi { /** * 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) @@ -174,10 +174,10 @@ public class OpenPgpApi { /** * Select key id for signing - *

+ * * optional extras: * String EXTRA_USER_ID - *

+ * * returned extras: * long EXTRA_SIGN_KEY_ID */ @@ -185,10 +185,10 @@ public class OpenPgpApi { /** * Get key ids based on given user ids (=emails) - *

+ * * required extras: * String[] EXTRA_USER_IDS - *

+ * * returned extras: * long[] RESULT_KEY_IDS */ @@ -197,26 +197,43 @@ public class OpenPgpApi { /** * 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 static final String ACTION_GET_KEY = "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 static final String ACTION_BACKUP = "org.openintents.openpgp.action.BACKUP"; + + /** + * Update the status of some Autocrypt peer, identified by their peer id. + * + * required extras: + * String EXTRA_AUTOCRYPT_PEER_ID (autocrypt peer id to update) + * AutocryptPeerUpdate EXTRA_AUTOCRYPT_PEER_UPDATE (actual peer update) + */ + public static final String ACTION_UPDATE_AUTOCRYPT_PEER = "org.openintents.openpgp.action.UPDATE_AUTOCRYPT_PEER"; + /* Intent extras */ public static final String EXTRA_API_VERSION = "api_version"; - // DEPRECATED!!! - public static final String EXTRA_ACCOUNT_NAME = "account_name"; - // 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) @@ -226,23 +243,37 @@ public class OpenPgpApi { public static final String RESULT_DETACHED_SIGNATURE = "detached_signature"; public static final String RESULT_SIGNATURE_MICALG = "signature_micalg"; - // ENCRYPT, SIGN_AND_ENCRYPT + // ENCRYPT, SIGN_AND_ENCRYPT, QUERY_AUTOCRYPT_STATUS public static final String EXTRA_USER_IDS = "user_ids"; public static final String EXTRA_KEY_IDS = "key_ids"; + public static final String EXTRA_KEY_IDS_SELECTED = "key_ids_selected"; public static final String EXTRA_SIGN_KEY_ID = "sign_key_id"; + + public static final String RESULT_KEYS_CONFIRMED = "keys_confirmed"; + public static final String RESULT_AUTOCRYPT_STATUS = "autocrypt_status"; + public static final int AUTOCRYPT_STATUS_UNAVAILABLE = 0; + public static final int AUTOCRYPT_STATUS_DISCOURAGE = 1; + public static final int AUTOCRYPT_STATUS_AVAILABLE = 2; + public static final int AUTOCRYPT_STATUS_MUTUAL = 3; + // optional extras: public static final String EXTRA_PASSPHRASE = "passphrase"; public static final String EXTRA_ORIGINAL_FILENAME = "original_filename"; public static final String EXTRA_ENABLE_COMPRESSION = "enable_compression"; - public static final String EXTRA_ENCRYPT_OPPORTUNISTIC = "opportunistic"; + public static final String EXTRA_OPPORTUNISTIC_ENCRYPTION = "opportunistic"; // GET_SIGN_KEY_ID public static final String EXTRA_USER_ID = "user_id"; // GET_KEY public static final String EXTRA_KEY_ID = "key_id"; + public static final String EXTRA_MINIMIZE = "minimize"; + public static final String EXTRA_MINIMIZE_USER_ID = "minimize_user_id"; public static final String RESULT_KEY_IDS = "key_ids"; + // BACKUP + public static final String EXTRA_BACKUP_SECRET = "backup_secret"; + /* Service Intent returns */ public static final String RESULT_CODE = "result_code"; @@ -258,10 +289,10 @@ public class OpenPgpApi { public static final String RESULT_INTENT = "intent"; // DECRYPT_VERIFY - public static final String EXTRA_DECRYPTION_RESULT = "decryption_result"; public static final String EXTRA_DETACHED_SIGNATURE = "detached_signature"; public static final String EXTRA_PROGRESS_MESSENGER = "progress_messenger"; public static final String EXTRA_DATA_LENGTH = "data_length"; + public static final String EXTRA_DECRYPTION_RESULT = "decryption_result"; public static final String EXTRA_SENDER_ADDRESS = "sender_address"; public static final String EXTRA_SUPPORT_OVERRIDE_CRYPTO_WARNING = "support_override_crpto_warning"; public static final String RESULT_SIGNATURE = "signature"; @@ -272,7 +303,11 @@ public class OpenPgpApi { // This will be the charset which was specified in the headers of ascii armored input, if any public static final String RESULT_CHARSET = "charset"; - // INTERNAL, should not be used + // UPDATE_AUTOCRYPT_PEER + public static final String EXTRA_AUTOCRYPT_PEER_ID = "autocrypt_peer_id"; + public static final String EXTRA_AUTOCRYPT_PEER_UPDATE = "autocrypt_peer_update"; + + // INTERNAL, must not be used public static final String EXTRA_CALL_UUID1 = "call_uuid1"; public static final String EXTRA_CALL_UUID2 = "call_uuid2"; From 42bd8683dd61b177b99f70be04f76d456b6f28d7 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 18 Apr 2017 17:09:26 +0200 Subject: [PATCH 02/11] autocrypt operations class --- .../fsck/k9/mail/internet/MimeUtility.java | 20 ++- .../fsck/k9/crypto/AutocryptOperations.java | 145 ++++++++++++++++++ .../k9/crypto/AutocryptOperationsTest.java | 118 ++++++++++++++ .../test/resources/autocrypt/no_autocrypt.eml | 11 ++ .../autocrypt/rsa2048-broken-base64.eml | 35 +++++ .../autocrypt/rsa2048-explicit-type.eml | 40 +++++ .../autocrypt/rsa2048-simple-to-bot.eml | 34 ++++ .../resources/autocrypt/rsa2048-simple.eml | 34 ++++ .../autocrypt/rsa2048-unknown-critical.eml | 35 +++++ .../rsa2048-unknown-non-critical.eml | 36 +++++ .../test/resources/autocrypt/unknown-type.eml | 36 +++++ 11 files changed, 543 insertions(+), 1 deletion(-) create mode 100644 k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java create mode 100644 k9mail/src/test/java/com/fsck/k9/crypto/AutocryptOperationsTest.java create mode 100644 k9mail/src/test/resources/autocrypt/no_autocrypt.eml create mode 100644 k9mail/src/test/resources/autocrypt/rsa2048-broken-base64.eml create mode 100644 k9mail/src/test/resources/autocrypt/rsa2048-explicit-type.eml create mode 100644 k9mail/src/test/resources/autocrypt/rsa2048-simple-to-bot.eml create mode 100644 k9mail/src/test/resources/autocrypt/rsa2048-simple.eml create mode 100644 k9mail/src/test/resources/autocrypt/rsa2048-unknown-critical.eml create mode 100644 k9mail/src/test/resources/autocrypt/rsa2048-unknown-non-critical.eml create mode 100644 k9mail/src/test/resources/autocrypt/unknown-type.eml diff --git a/k9mail-library/src/main/java/com/fsck/k9/mail/internet/MimeUtility.java b/k9mail-library/src/main/java/com/fsck/k9/mail/internet/MimeUtility.java index 66ab042dc..a901489bb 100644 --- a/k9mail-library/src/main/java/com/fsck/k9/mail/internet/MimeUtility.java +++ b/k9mail-library/src/main/java/com/fsck/k9/mail/internet/MimeUtility.java @@ -4,7 +4,9 @@ package com.fsck.k9.mail.internet; import java.io.IOException; import java.io.InputStream; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; import java.util.regex.Pattern; import android.support.annotation.NonNull; @@ -15,7 +17,6 @@ import com.fsck.k9.mail.Message; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.Multipart; import com.fsck.k9.mail.Part; - import org.apache.james.mime4j.codec.Base64InputStream; import org.apache.james.mime4j.codec.QuotedPrintableInputStream; import org.apache.james.mime4j.util.MimeUtil; @@ -953,6 +954,23 @@ public class MimeUtility { return null; } + public static Map getAllHeaderParameters(String headerValue) { + Map result = new HashMap<>(); + + headerValue = headerValue.replaceAll("\r|\n", ""); + String[] parts = headerValue.split(";"); + for (String part : parts) { + String[] partParts = part.split("=", 2); + if (partParts.length == 2) { + String parameterName = partParts[0].trim().toLowerCase(Locale.US); + String parameterValue = partParts[1].trim(); + result.put(parameterName, parameterValue); + } + } + return result; + } + + public static Part findFirstPartByMimeType(Part part, String mimeType) { if (part.getBody() instanceof Multipart) { Multipart multipart = (Multipart)part.getBody(); diff --git a/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java b/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java new file mode 100644 index 000000000..e0412b90a --- /dev/null +++ b/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java @@ -0,0 +1,145 @@ +package com.fsck.k9.crypto; + + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.Map; + +import android.content.Intent; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; + +import com.fsck.k9.mail.internet.MimeMessage; +import com.fsck.k9.mail.internet.MimeUtility; +import okio.ByteString; +import org.openintents.openpgp.AutocryptPeerUpdate; +import org.openintents.openpgp.util.OpenPgpApi; +import timber.log.Timber; + + +public class AutocryptOperations { + private static final String AUTOCRYPT_PARAM_KEY_DATA = "key"; + private static final String AUTOCRYPT_PARAM_TO = "to"; + private static final String AUTOCRYPT_HEADER = "Autocrypt"; + private static final String AUTOCRYPT_PARAM_TYPE = "type"; + + + public AutocryptOperations() { + } + + + private boolean addAutocryptPeerUpdateToIntentIfPresent(MimeMessage currentMessage, Intent intent) { + AutocryptHeader autocryptHeader = getValidAutocryptHeader(currentMessage); + if (autocryptHeader == null) { + return false; + } + + String messageFromAddress = currentMessage.getFrom()[0].getAddress(); + if (!autocryptHeader.to.equalsIgnoreCase(messageFromAddress)) { + return false; + } + + Date messageDate = currentMessage.getSentDate(); + Date internalDate = currentMessage.getInternalDate(); + Date effectiveDate = messageDate.before(internalDate) ? messageDate : internalDate; + + AutocryptPeerUpdate data = AutocryptPeerUpdate.createAutocryptPeerUpdate(autocryptHeader.keyData, effectiveDate); + intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID, messageFromAddress); + intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE, data); + return true; + } + + public void processCleartextMessage(OpenPgpApi openPgpApi, MimeMessage currentMessage) { + Intent intent = new Intent(OpenPgpApi.ACTION_UPDATE_AUTOCRYPT_PEER); + boolean hasInlineKeyData = addAutocryptPeerUpdateToIntentIfPresent(currentMessage, intent); + if (hasInlineKeyData) { + openPgpApi.executeApi(intent, (InputStream) null, null); + } + } + + @Nullable + @VisibleForTesting + AutocryptHeader getValidAutocryptHeader(MimeMessage currentMessage) { + String[] headers = currentMessage.getHeader(AUTOCRYPT_HEADER); + ArrayList autocryptHeaders = parseAllAutocryptHeaders(headers); + + boolean isSingleValidHeader = autocryptHeaders.size() == 1; + return isSingleValidHeader ? autocryptHeaders.get(0) : null; + } + + @NonNull + private ArrayList parseAllAutocryptHeaders(String[] headers) { + ArrayList autocryptHeaders = new ArrayList<>(); + for (String header : headers) { + AutocryptHeader autocryptHeader = parseAutocryptHeader(header); + if (autocryptHeader != null) { + autocryptHeaders.add(autocryptHeader); + } + } + return autocryptHeaders; + } + + @Nullable + private AutocryptHeader parseAutocryptHeader(String headerValue) { + Map parameters = MimeUtility.getAllHeaderParameters(headerValue); + + String type = parameters.remove(AUTOCRYPT_PARAM_TYPE); + if (type != null && !type.equals("p")) { + Timber.e("autocrypt: unsupported type parameter %s", type); + return null; + } + + String base64KeyData = parameters.remove(AUTOCRYPT_PARAM_KEY_DATA); + if (base64KeyData == null) { + Timber.e("autocrypt: missing key parameter"); + return null; + } + + ByteString byteString = ByteString.decodeBase64(base64KeyData); + if (byteString == null) { + Timber.e("autocrypt: error parsing base64 data"); + return null; + } + + String to = parameters.remove(AUTOCRYPT_PARAM_TO); + if (to == null) { + Timber.e("autocrypt: no to header!"); + return null; + } + + + if (hasCriticalParameters(parameters)) { + return null; + } + + return new AutocryptHeader(parameters, to, byteString.toByteArray()); + } + + private boolean hasCriticalParameters(Map parameters) { + for (String parameterName : parameters.keySet()) { + if (parameterName != null && !parameterName.startsWith("_")) { + return true; + } + } + return false; + } + + public boolean hasAutocryptHeader(MimeMessage currentMessage) { + return currentMessage.getHeader(AUTOCRYPT_HEADER).length > 0; + } + + @VisibleForTesting + class AutocryptHeader { + final byte[] keyData; + final String to; + final Map parameters; + + private AutocryptHeader(Map parameters, String to, byte[] keyData) { + this.parameters = parameters; + this.to = to; + this.keyData = keyData; + } + } +} diff --git a/k9mail/src/test/java/com/fsck/k9/crypto/AutocryptOperationsTest.java b/k9mail/src/test/java/com/fsck/k9/crypto/AutocryptOperationsTest.java new file mode 100644 index 000000000..af044a219 --- /dev/null +++ b/k9mail/src/test/java/com/fsck/k9/crypto/AutocryptOperationsTest.java @@ -0,0 +1,118 @@ +package com.fsck.k9.crypto; + + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +import com.fsck.k9.crypto.AutocryptOperations.AutocryptHeader; +import com.fsck.k9.mail.MessagingException; +import com.fsck.k9.mail.internet.BinaryTempFileBody; +import com.fsck.k9.mail.internet.MimeMessage; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE, sdk = 21) +public class AutocryptOperationsTest { + AutocryptOperations autocryptOperations = new AutocryptOperations(); + + @Before + public void setUp() throws Exception { + BinaryTempFileBody.setTempDirectory(RuntimeEnvironment.application.getCacheDir()); + } + + // Test cases taken from: https://github.com/mailencrypt/autocrypt/tree/master/src/tests/data + + @Test + public void getValidAutocryptHeader__withNoHeader__shouldReturnNull() throws Exception { + MimeMessage message = parseFromResource("autocrypt/no_autocrypt.eml"); + + AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); + + assertNull(autocryptHeader); + } + + @Test + public void getValidAutocryptHeader__withBrokenBase64__shouldReturnNull() throws Exception { + MimeMessage message = parseFromResource("autocrypt/rsa2048-broken-base64.eml"); + + AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); + + assertNull(autocryptHeader); + } + + @Test + public void getValidAutocryptHeader__withSimpleAutocrypt() throws Exception { + MimeMessage message = parseFromResource("autocrypt/rsa2048-simple.eml"); + + AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); + + assertNotNull(autocryptHeader); + assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.to); + assertEquals(0, autocryptHeader.parameters.size()); + assertEquals(1225, autocryptHeader.keyData.length); + } + + @Test + public void getValidAutocryptHeader__withExplicitType() throws Exception { + MimeMessage message = parseFromResource("autocrypt/rsa2048-explicit-type.eml"); + + AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); + + assertNotNull(autocryptHeader); + assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.to); + assertEquals(0, autocryptHeader.parameters.size()); + } + + @Test + public void getValidAutocryptHeader__withUnknownType__shouldReturnNull() throws Exception { + MimeMessage message = parseFromResource("autocrypt/unknown-type.eml"); + + AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); + + assertNull(autocryptHeader); + } + + @Test + public void getValidAutocryptHeader__withUnknownCriticalHeader__shouldReturnNull() throws Exception { + MimeMessage message = parseFromResource("autocrypt/rsa2048-unknown-critical.eml"); + + AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); + + assertNull(autocryptHeader); + } + + @Test + public void getValidAutocryptHeader__withUnknownNonCriticalHeader() throws Exception { + MimeMessage message = parseFromResource("autocrypt/rsa2048-unknown-non-critical.eml"); + + AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); + + assertNotNull(autocryptHeader); + assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.to); + assertEquals(1, autocryptHeader.parameters.size()); + assertEquals("ignore", autocryptHeader.parameters.get("_monkey")); + } + + private MimeMessage parseFromResource(String resourceName) throws IOException, MessagingException { + InputStream inputStream = readFromResourceFile(resourceName); + return MimeMessage.parseMimeMessage(inputStream, false); + } + + private InputStream readFromResourceFile(String name) throws FileNotFoundException { + return new FileInputStream(RuntimeEnvironment.application.getPackageResourcePath() + "/src/test/resources/" + name); + } + + +} \ No newline at end of file diff --git a/k9mail/src/test/resources/autocrypt/no_autocrypt.eml b/k9mail/src/test/resources/autocrypt/no_autocrypt.eml new file mode 100644 index 000000000..947af1ce5 --- /dev/null +++ b/k9mail/src/test/resources/autocrypt/no_autocrypt.eml @@ -0,0 +1,11 @@ +From: Alice +To: Bob +Subject: INBOME with invalid type attribute +Date: Sat, 17 Dec 2016 10:51:48 +0100 +Message-ID: +MIME-Version: 1.0 +Content-Type: text/plain + +This message contains no INBOME header + +An agent capable of INBOME level 0 should not try to parse it as a header and crash. diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-broken-base64.eml b/k9mail/src/test/resources/autocrypt/rsa2048-broken-base64.eml new file mode 100644 index 000000000..0eca32499 --- /dev/null +++ b/k9mail/src/test/resources/autocrypt/rsa2048-broken-base64.eml @@ -0,0 +1,35 @@ +From: Alice +To: Bob +Subject: an INBOME RSA test +INBOME: to=alice@testsuite.autocrypt.org; key= + m!@#$%hVF+ABCADu17FBUgA3mCemeKbNaBTyWe3VGxjbu7fUyHgdLK7i3tnd7IRtxQy/AEN2t6Vq + 0/xeZEAKYRInsHI/HjvmhqPeWFzipk71jRQ02WUY1pZytFjYNIrTdMk4eLYdC1N0go83PU33V4R8 + fc2fWHD8N5JPsDH2xOB6WNWkMPxgMbtGIa0QTx7TINhDif4/1/VcrX3wz1gZ6xYI+sujbC54iBZo + qbEfu4SFVvp53d+a1plxBzuZ/X6nqJqcysiS7ORMieBvU6W/mVeiAxwN4qcAI5s+rGmRnP8ltONK + /P1ScH6lmELgqm8Z/M0wdiYgywme/bdEQOg3s0S/8nCIFmwUchN7ABEBAAG0HWFsaWNlQHRlc3Rz + dWl0ZS5hdXRvY3J5cHQub3JniQFOBBMBCAA4FiEEfi47NkGai9tG9hBruvxTPNmTvX8FAlhVF+AC + GwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQuvxTPNmTvX/k4wf+JJZ0M0rZeAXbnxdR6HDU + ZYL734Z8x/HRpz3vzK4VQQJ4oIbUQPwydZmAlTlglQY48IWWOdJnYvn2pIhlTM/T8q9ZfmOyp6i1 + jxFCPT+2ma4DjNOqYFhfnULE/MYc6xeVaBcwGj7yvAW7YY7156/wDo6+9TCd/a9mzOFCGS0yQoRa + K3uDajA+G/SmbC8t/3X8+5sapvi9Ru0HNkIzaj1jhH+kW6628E7nkf9aN9LodXHfs1UtfuLqM8VG + Ysk9474x9QxbsrJ4YvXeFwM9zAs+Pvj4lnpH/0WOU8jJc3uarluGH58kTHM5/5+p0TeMpOHX7OEw + JndsBOV9gFc6FMx4hLkBDQRYVRfiAQgA3ad+Aat4UY8xvQQutLYb8e417XZN1zVmKypyReB0l0Zf + HA6Qc7uxnJQ7dzIEZAxdnjvTYJaCFrOCBXAyPHpShVMpKqQP+kBBY/WiC3BSUALR3xqp7k5/sjLD + +K4dAacXEc7nyXP5o5+oqBXEH8Ls5X440c9A3EdsvVlncvSW5ILLItlFHmQd6f1ynnjK+FQwYJRJ + ypDuqJpYkA1vn7+XxeQShpX105rM3C8tJUxRAP3QFimenn4Zm2BDhQpCneuBt239rkXOAXsR0PnJ + fV8eNAEsE8IIqnPoSlBme5DZAri69+joYmTeSKGuj4aoxzDlx1AQigwpMISLciTXLnJypwARAQAB + iQE2BBgBCAAgFiEEfi47NkGai9tG9hBruvxTPNmTvX8FAlhVF+ICGwwACgkQuvxTPNmTvX8dOwf9 + H72BGoYJkuuFrbQ6F/mH7gG9z3ytQHRD2Z0ja+3O7YnJBpotHFFjF7yHGj0FtmQR0Q7KnhkJ/3mv + fkvuaH3Gcjli/E7VASastuFDFkGANLmGZVGQQ2iTYFG1aejjtGb01vcaPrgE9WDueMB+Pn6/QbDc + 5SWCrVWrRFZKrwbAGw35GySoFYpxXyCNsk6q6Db56plllPZjrYj7axF0yN536D1ntEVFDOdKZq8x + Tb9P/4Tq9NKRLE4+aO6qCqEOz+V1OeOvYLw58BfnzXY8rXF93D/86YLyilv6p5WGaS/cRhIzr+Xq + +qBLD/vW+dh72e8MvcduX3tXV3Vkg0mkGekdOw== +Date: Sat, 17 Dec 2016 10:07:48 +0100 +Message-ID: +MIME-Version: 1.0 +Content-Type: text/plain + +This contains an INBOME header with invalid characters in the base64 key +data. + +This header should be ignored by all user agents. diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-explicit-type.eml b/k9mail/src/test/resources/autocrypt/rsa2048-explicit-type.eml new file mode 100644 index 000000000..c7d8c941c --- /dev/null +++ b/k9mail/src/test/resources/autocrypt/rsa2048-explicit-type.eml @@ -0,0 +1,40 @@ +From: Alice +To: Bob +Subject: inbome rsa2048 with correct type +INBOME: to=alice@testsuite.autocrypt.org; type=p; key= + mQENBFhVF+ABCADu17FBUgA3mCemeKbNaBTyWe3VGxjbu7fUyHgdLK7i3tnd7IRtxQy/AEN2t6Vq + 0/xeZEAKYRInsHI/HjvmhqPeWFzipk71jRQ02WUY1pZytFjYNIrTdMk4eLYdC1N0go83PU33V4R8 + fc2fWHD8N5JPsDH2xOB6WNWkMPxgMbtGIa0QTx7TINhDif4/1/VcrX3wz1gZ6xYI+sujbC54iBZo + qbEfu4SFVvp53d+a1plxBzuZ/X6nqJqcysiS7ORMieBvU6W/mVeiAxwN4qcAI5s+rGmRnP8ltONK + /P1ScH6lmELgqm8Z/M0wdiYgywme/bdEQOg3s0S/8nCIFmwUchN7ABEBAAG0HWFsaWNlQHRlc3Rz + dWl0ZS5hdXRvY3J5cHQub3JniQFOBBMBCAA4FiEEfi47NkGai9tG9hBruvxTPNmTvX8FAlhVF+AC + GwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQuvxTPNmTvX/k4wf+JJZ0M0rZeAXbnxdR6HDU + ZYL734Z8x/HRpz3vzK4VQQJ4oIbUQPwydZmAlTlglQY48IWWOdJnYvn2pIhlTM/T8q9ZfmOyp6i1 + jxFCPT+2ma4DjNOqYFhfnULE/MYc6xeVaBcwGj7yvAW7YY7156/wDo6+9TCd/a9mzOFCGS0yQoRa + K3uDajA+G/SmbC8t/3X8+5sapvi9Ru0HNkIzaj1jhH+kW6628E7nkf9aN9LodXHfs1UtfuLqM8VG + Ysk9474x9QxbsrJ4YvXeFwM9zAs+Pvj4lnpH/0WOU8jJc3uarluGH58kTHM5/5+p0TeMpOHX7OEw + JndsBOV9gFc6FMx4hLkBDQRYVRfiAQgA3ad+Aat4UY8xvQQutLYb8e417XZN1zVmKypyReB0l0Zf + HA6Qc7uxnJQ7dzIEZAxdnjvTYJaCFrOCBXAyPHpShVMpKqQP+kBBY/WiC3BSUALR3xqp7k5/sjLD + +K4dAacXEc7nyXP5o5+oqBXEH8Ls5X440c9A3EdsvVlncvSW5ILLItlFHmQd6f1ynnjK+FQwYJRJ + ypDuqJpYkA1vn7+XxeQShpX105rM3C8tJUxRAP3QFimenn4Zm2BDhQpCneuBt239rkXOAXsR0PnJ + fV8eNAEsE8IIqnPoSlBme5DZAri69+joYmTeSKGuj4aoxzDlx1AQigwpMISLciTXLnJypwARAQAB + iQE2BBgBCAAgFiEEfi47NkGai9tG9hBruvxTPNmTvX8FAlhVF+ICGwwACgkQuvxTPNmTvX8dOwf9 + H72BGoYJkuuFrbQ6F/mH7gG9z3ytQHRD2Z0ja+3O7YnJBpotHFFjF7yHGj0FtmQR0Q7KnhkJ/3mv + fkvuaH3Gcjli/E7VASastuFDFkGANLmGZVGQQ2iTYFG1aejjtGb01vcaPrgE9WDueMB+Pn6/QbDc + 5SWCrVWrRFZKrwbAGw35GySoFYpxXyCNsk6q6Db56plllPZjrYj7axF0yN536D1ntEVFDOdKZq8x + Tb9P/4Tq9NKRLE4+aO6qCqEOz+V1OeOvYLw58BfnzXY8rXF93D/86YLyilv6p5WGaS/cRhIzr+Xq + +qBLD/vW+dh72e8MvcduX3tXV3Vkg0mkGekdOw== +Date: Sat, 17 Dec 2016 10:49:02 +0100 +Message-ID: +MIME-Version: 1.0 +Content-Type: text/plain + +This message contains the standard INBOME header, but includes an +explicit "type=p" attribute. This is the default value of type, so it +should not be necessary to include this attribute. + +"p" signifies that the key value is a specialized subset of OpenPGP. + +This should be accepted by any agent capable of INBOME level 0. + + --dkg diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-simple-to-bot.eml b/k9mail/src/test/resources/autocrypt/rsa2048-simple-to-bot.eml new file mode 100644 index 000000000..452ea7fc8 --- /dev/null +++ b/k9mail/src/test/resources/autocrypt/rsa2048-simple-to-bot.eml @@ -0,0 +1,34 @@ +From: Alice +To: Autocrypt-Bot +Subject: an INBOME RSA test +INBOME: to=alice@testsuite.autocrypt.org; key= + mQENBFhVF+ABCADu17FBUgA3mCemeKbNaBTyWe3VGxjbu7fUyHgdLK7i3tnd7IRtxQy/AEN2t6Vq + 0/xeZEAKYRInsHI/HjvmhqPeWFzipk71jRQ02WUY1pZytFjYNIrTdMk4eLYdC1N0go83PU33V4R8 + fc2fWHD8N5JPsDH2xOB6WNWkMPxgMbtGIa0QTx7TINhDif4/1/VcrX3wz1gZ6xYI+sujbC54iBZo + qbEfu4SFVvp53d+a1plxBzuZ/X6nqJqcysiS7ORMieBvU6W/mVeiAxwN4qcAI5s+rGmRnP8ltONK + /P1ScH6lmELgqm8Z/M0wdiYgywme/bdEQOg3s0S/8nCIFmwUchN7ABEBAAG0HWFsaWNlQHRlc3Rz + dWl0ZS5hdXRvY3J5cHQub3JniQFOBBMBCAA4FiEEfi47NkGai9tG9hBruvxTPNmTvX8FAlhVF+AC + GwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQuvxTPNmTvX/k4wf+JJZ0M0rZeAXbnxdR6HDU + ZYL734Z8x/HRpz3vzK4VQQJ4oIbUQPwydZmAlTlglQY48IWWOdJnYvn2pIhlTM/T8q9ZfmOyp6i1 + jxFCPT+2ma4DjNOqYFhfnULE/MYc6xeVaBcwGj7yvAW7YY7156/wDo6+9TCd/a9mzOFCGS0yQoRa + K3uDajA+G/SmbC8t/3X8+5sapvi9Ru0HNkIzaj1jhH+kW6628E7nkf9aN9LodXHfs1UtfuLqM8VG + Ysk9474x9QxbsrJ4YvXeFwM9zAs+Pvj4lnpH/0WOU8jJc3uarluGH58kTHM5/5+p0TeMpOHX7OEw + JndsBOV9gFc6FMx4hLkBDQRYVRfiAQgA3ad+Aat4UY8xvQQutLYb8e417XZN1zVmKypyReB0l0Zf + HA6Qc7uxnJQ7dzIEZAxdnjvTYJaCFrOCBXAyPHpShVMpKqQP+kBBY/WiC3BSUALR3xqp7k5/sjLD + +K4dAacXEc7nyXP5o5+oqBXEH8Ls5X440c9A3EdsvVlncvSW5ILLItlFHmQd6f1ynnjK+FQwYJRJ + ypDuqJpYkA1vn7+XxeQShpX105rM3C8tJUxRAP3QFimenn4Zm2BDhQpCneuBt239rkXOAXsR0PnJ + fV8eNAEsE8IIqnPoSlBme5DZAri69+joYmTeSKGuj4aoxzDlx1AQigwpMISLciTXLnJypwARAQAB + iQE2BBgBCAAgFiEEfi47NkGai9tG9hBruvxTPNmTvX8FAlhVF+ICGwwACgkQuvxTPNmTvX8dOwf9 + H72BGoYJkuuFrbQ6F/mH7gG9z3ytQHRD2Z0ja+3O7YnJBpotHFFjF7yHGj0FtmQR0Q7KnhkJ/3mv + fkvuaH3Gcjli/E7VASastuFDFkGANLmGZVGQQ2iTYFG1aejjtGb01vcaPrgE9WDueMB+Pn6/QbDc + 5SWCrVWrRFZKrwbAGw35GySoFYpxXyCNsk6q6Db56plllPZjrYj7axF0yN536D1ntEVFDOdKZq8x + Tb9P/4Tq9NKRLE4+aO6qCqEOz+V1OeOvYLw58BfnzXY8rXF93D/86YLyilv6p5WGaS/cRhIzr+Xq + +qBLD/vW+dh72e8MvcduX3tXV3Vkg0mkGekdOw== +Date: Sat, 17 Dec 2016 10:07:48 +0100 +Message-ID: +MIME-Version: 1.0 +Content-Type: text/plain + +This contains a default, minimal INBOME header using an RSA 2048 key. + +This should be importable and valid by any agent supporting INBOME level 0. diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-simple.eml b/k9mail/src/test/resources/autocrypt/rsa2048-simple.eml new file mode 100644 index 000000000..96440dbdc --- /dev/null +++ b/k9mail/src/test/resources/autocrypt/rsa2048-simple.eml @@ -0,0 +1,34 @@ +From: Alice +To: Bob +Subject: an INBOME RSA test +INBOME: to=alice@testsuite.autocrypt.org; key= + mQENBFhVF+ABCADu17FBUgA3mCemeKbNaBTyWe3VGxjbu7fUyHgdLK7i3tnd7IRtxQy/AEN2t6Vq + 0/xeZEAKYRInsHI/HjvmhqPeWFzipk71jRQ02WUY1pZytFjYNIrTdMk4eLYdC1N0go83PU33V4R8 + fc2fWHD8N5JPsDH2xOB6WNWkMPxgMbtGIa0QTx7TINhDif4/1/VcrX3wz1gZ6xYI+sujbC54iBZo + qbEfu4SFVvp53d+a1plxBzuZ/X6nqJqcysiS7ORMieBvU6W/mVeiAxwN4qcAI5s+rGmRnP8ltONK + /P1ScH6lmELgqm8Z/M0wdiYgywme/bdEQOg3s0S/8nCIFmwUchN7ABEBAAG0HWFsaWNlQHRlc3Rz + dWl0ZS5hdXRvY3J5cHQub3JniQFOBBMBCAA4FiEEfi47NkGai9tG9hBruvxTPNmTvX8FAlhVF+AC + GwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQuvxTPNmTvX/k4wf+JJZ0M0rZeAXbnxdR6HDU + ZYL734Z8x/HRpz3vzK4VQQJ4oIbUQPwydZmAlTlglQY48IWWOdJnYvn2pIhlTM/T8q9ZfmOyp6i1 + jxFCPT+2ma4DjNOqYFhfnULE/MYc6xeVaBcwGj7yvAW7YY7156/wDo6+9TCd/a9mzOFCGS0yQoRa + K3uDajA+G/SmbC8t/3X8+5sapvi9Ru0HNkIzaj1jhH+kW6628E7nkf9aN9LodXHfs1UtfuLqM8VG + Ysk9474x9QxbsrJ4YvXeFwM9zAs+Pvj4lnpH/0WOU8jJc3uarluGH58kTHM5/5+p0TeMpOHX7OEw + JndsBOV9gFc6FMx4hLkBDQRYVRfiAQgA3ad+Aat4UY8xvQQutLYb8e417XZN1zVmKypyReB0l0Zf + HA6Qc7uxnJQ7dzIEZAxdnjvTYJaCFrOCBXAyPHpShVMpKqQP+kBBY/WiC3BSUALR3xqp7k5/sjLD + +K4dAacXEc7nyXP5o5+oqBXEH8Ls5X440c9A3EdsvVlncvSW5ILLItlFHmQd6f1ynnjK+FQwYJRJ + ypDuqJpYkA1vn7+XxeQShpX105rM3C8tJUxRAP3QFimenn4Zm2BDhQpCneuBt239rkXOAXsR0PnJ + fV8eNAEsE8IIqnPoSlBme5DZAri69+joYmTeSKGuj4aoxzDlx1AQigwpMISLciTXLnJypwARAQAB + iQE2BBgBCAAgFiEEfi47NkGai9tG9hBruvxTPNmTvX8FAlhVF+ICGwwACgkQuvxTPNmTvX8dOwf9 + H72BGoYJkuuFrbQ6F/mH7gG9z3ytQHRD2Z0ja+3O7YnJBpotHFFjF7yHGj0FtmQR0Q7KnhkJ/3mv + fkvuaH3Gcjli/E7VASastuFDFkGANLmGZVGQQ2iTYFG1aejjtGb01vcaPrgE9WDueMB+Pn6/QbDc + 5SWCrVWrRFZKrwbAGw35GySoFYpxXyCNsk6q6Db56plllPZjrYj7axF0yN536D1ntEVFDOdKZq8x + Tb9P/4Tq9NKRLE4+aO6qCqEOz+V1OeOvYLw58BfnzXY8rXF93D/86YLyilv6p5WGaS/cRhIzr+Xq + +qBLD/vW+dh72e8MvcduX3tXV3Vkg0mkGekdOw== +Date: Sat, 17 Dec 2016 10:07:48 +0100 +Message-ID: +MIME-Version: 1.0 +Content-Type: text/plain + +This contains a default, minimal INBOME header using an RSA 2048 key. + +This should be importable and valid by any agent supporting INBOME level 0. diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-unknown-critical.eml b/k9mail/src/test/resources/autocrypt/rsa2048-unknown-critical.eml new file mode 100644 index 000000000..32acf0d88 --- /dev/null +++ b/k9mail/src/test/resources/autocrypt/rsa2048-unknown-critical.eml @@ -0,0 +1,35 @@ +From: Alice +To: Bob +Subject: another inbome with rsa2048 but with an unknown critical attribute +INBOME: to=alice@testsuite.autocrypt.org; danger=do-not-use; key= + mQENBFhVGA8BCADK+qTRkAfax0LtJ6RiyxzuAFyIohBTwvtcOM2sd/tRmWq1eyNif5AGDnc1+b6X + zJ6l3BXiYM/8qXU/F04UA5BP05SgIqXjqT5I13blrydjKtUbZFchK7lJU7cyDbar+TH70DZURSQm + MusCj0+fdx6hx8y4LSOM68rjwVeq7JXAPU78QQsYgMrbtkf5mZWUquDdb7tEoxU+PcNifvtvuHF2 + ILv09a4Fi8thJG4i/3LxMFtmMLIiZWLfk5KpXAKrOy436e1LCm3vesALcihPNppb803dgBqpvvEE + 9W7sg5NUy3P8+fTEuvI8HYYd+lEvYe2ojm4HVTts4YFHmzaGVzHLABEBAAG0HVRoaXMgc2hvdWxk + IG5ldmVyIGJlIGltcG9ydGVkiQFOBBMBCAA4FiEE0uuMX0KSMgVBfC/25MZusLe5gWMFAlhVGA8C + GwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQ5MZusLe5gWN9Hwf+PpLCCV7TiGc1nqIxLMTs + O84PVLSQZB642/QhLoMYXQ5iqty5H2FqGuK+uWLCnM+yIMDkcJC3ayWfa06fs3JOipVKlMh8hHnU + 6/FHJB+3eZrc4lhh5B67Vi8Xg43pTP+I9ct/PlbHvD9kYw+DpcmCz0XILhaUP0R1oQ6M5KI49uLg + LAdNczcEtcw3A/hZ5ZTUe3o3gav0XDBXFCgGjkI+CaMjKb/HjgNM9YsrGxUxH1RFMYqTfrmCklHD + EboQc1Qtzi5rIwzVR3zSryve9KHH75TCfDApghwUBKSLNh374hjTFj5v5kPAxG3njX6EOqHS/UVX + Mn5aEVn0n6S1y+DJZLkBDQRYVRgRAQgAzQgD/CluB1wuBeI8qaqmIxG8epHCPstQ4kee6FuFWi3F + Lqtyk1R9tB4UL40gpEkpzB+qYms/zs9SeicuNcXoXA4bMcNGDFz3mZ1d9qG2izgC19e9p50oXiMY + cr8GM1Qcb77dmxlk829cBpr+X7NDKJy9VMGsqNYukgFDnNIzty0oMdCLSzpqi3UtXtCGYDqIiltU + aT8XdMAvddr6Scgpkz3wrqi/bVagc+q4IdKL0r8iL7o3EnTf/5Dc2XUaCFJLCa3Rk6oat5kTWjan + sp/K5k/VzSDcESji8n6xl0OzD2okhmX8iJZg1hhyI8hNmtW3boe51Hkkdlj+wC8Y2Fgh4QARAQAB + iQE2BBgBCAAgFiEE0uuMX0KSMgVBfC/25MZusLe5gWMFAlhVGBECGwwACgkQ5MZusLe5gWOn/Qf/ + aeV7CqZW/YN4/LhXjJG7i+iDJYv/9Lr12dvgjO/sOlmDPHkEzXPMLKalm0biMPN7E1woQzcKt7Qy + eF/CRcVKK1TM6wdClOj2jErnWyx85/uZfnG9QRD41rhInk891A8LGebPZ6DJeJR/uwzMniEgNnKN + AMuGy95ckwlM3AfwzsKPTUUFnBAmSwWfMLRxjZPNefeo1Ic8mMRAT3d5sfDUx/4wm8tyiNLuOSkm + Ej6ONYpESD2sJGMo3ZY96pkzir7ZH++4mH6PwZg1ZT2nO+0PtaB9DHRGfBrzH85d4aLFZD9txx3p + ewabrNpYI/cJu9hUTaTM7wZaG5kmfStwihKYUg== +Date: Sat, 17 Dec 2016 10:32:49 +0100 +Message-ID: +MIME-Version: 1.0 +Content-Type: text/plain + +This message contains an INBOME header with RSA 2048, but with an +unknown critical attribute. Agents that are compatible with INBOME +level 0 should ignore this header because of the unknown critical +attribute. diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-unknown-non-critical.eml b/k9mail/src/test/resources/autocrypt/rsa2048-unknown-non-critical.eml new file mode 100644 index 000000000..af1b5d8fa --- /dev/null +++ b/k9mail/src/test/resources/autocrypt/rsa2048-unknown-non-critical.eml @@ -0,0 +1,36 @@ +From: Alice +To: Bob +Subject: inbome: rsa 2048 with unknown non-critical attribute +INBOME: to=alice@testsuite.autocrypt.org; _monkey=ignore; key= + mQENBFhVF+ABCADu17FBUgA3mCemeKbNaBTyWe3VGxjbu7fUyHgdLK7i3tnd7IRtxQy/AEN2t6Vq + 0/xeZEAKYRInsHI/HjvmhqPeWFzipk71jRQ02WUY1pZytFjYNIrTdMk4eLYdC1N0go83PU33V4R8 + fc2fWHD8N5JPsDH2xOB6WNWkMPxgMbtGIa0QTx7TINhDif4/1/VcrX3wz1gZ6xYI+sujbC54iBZo + qbEfu4SFVvp53d+a1plxBzuZ/X6nqJqcysiS7ORMieBvU6W/mVeiAxwN4qcAI5s+rGmRnP8ltONK + /P1ScH6lmELgqm8Z/M0wdiYgywme/bdEQOg3s0S/8nCIFmwUchN7ABEBAAG0HWFsaWNlQHRlc3Rz + dWl0ZS5hdXRvY3J5cHQub3JniQFOBBMBCAA4FiEEfi47NkGai9tG9hBruvxTPNmTvX8FAlhVF+AC + GwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQuvxTPNmTvX/k4wf+JJZ0M0rZeAXbnxdR6HDU + ZYL734Z8x/HRpz3vzK4VQQJ4oIbUQPwydZmAlTlglQY48IWWOdJnYvn2pIhlTM/T8q9ZfmOyp6i1 + jxFCPT+2ma4DjNOqYFhfnULE/MYc6xeVaBcwGj7yvAW7YY7156/wDo6+9TCd/a9mzOFCGS0yQoRa + K3uDajA+G/SmbC8t/3X8+5sapvi9Ru0HNkIzaj1jhH+kW6628E7nkf9aN9LodXHfs1UtfuLqM8VG + Ysk9474x9QxbsrJ4YvXeFwM9zAs+Pvj4lnpH/0WOU8jJc3uarluGH58kTHM5/5+p0TeMpOHX7OEw + JndsBOV9gFc6FMx4hLkBDQRYVRfiAQgA3ad+Aat4UY8xvQQutLYb8e417XZN1zVmKypyReB0l0Zf + HA6Qc7uxnJQ7dzIEZAxdnjvTYJaCFrOCBXAyPHpShVMpKqQP+kBBY/WiC3BSUALR3xqp7k5/sjLD + +K4dAacXEc7nyXP5o5+oqBXEH8Ls5X440c9A3EdsvVlncvSW5ILLItlFHmQd6f1ynnjK+FQwYJRJ + ypDuqJpYkA1vn7+XxeQShpX105rM3C8tJUxRAP3QFimenn4Zm2BDhQpCneuBt239rkXOAXsR0PnJ + fV8eNAEsE8IIqnPoSlBme5DZAri69+joYmTeSKGuj4aoxzDlx1AQigwpMISLciTXLnJypwARAQAB + iQE2BBgBCAAgFiEEfi47NkGai9tG9hBruvxTPNmTvX8FAlhVF+ICGwwACgkQuvxTPNmTvX8dOwf9 + H72BGoYJkuuFrbQ6F/mH7gG9z3ytQHRD2Z0ja+3O7YnJBpotHFFjF7yHGj0FtmQR0Q7KnhkJ/3mv + fkvuaH3Gcjli/E7VASastuFDFkGANLmGZVGQQ2iTYFG1aejjtGb01vcaPrgE9WDueMB+Pn6/QbDc + 5SWCrVWrRFZKrwbAGw35GySoFYpxXyCNsk6q6Db56plllPZjrYj7axF0yN536D1ntEVFDOdKZq8x + Tb9P/4Tq9NKRLE4+aO6qCqEOz+V1OeOvYLw58BfnzXY8rXF93D/86YLyilv6p5WGaS/cRhIzr+Xq + +qBLD/vW+dh72e8MvcduX3tXV3Vkg0mkGekdOw== +Date: Sat, 17 Dec 2016 10:34:30 +0100 +Message-ID: +MIME-Version: 1.0 +Content-Type: text/plain + +This message contains an INBOME header with RSA 2048, but with an +unknown non-critical attribute. Agents that are compatible with +INBOME level 0 should accept this header while ignoring the unknown +attribute. + diff --git a/k9mail/src/test/resources/autocrypt/unknown-type.eml b/k9mail/src/test/resources/autocrypt/unknown-type.eml new file mode 100644 index 000000000..7a79a26aa --- /dev/null +++ b/k9mail/src/test/resources/autocrypt/unknown-type.eml @@ -0,0 +1,36 @@ +From: Alice +To: Bob +Subject: INBOME with invalid type attribute +INBOME: to=alice@testsuite.autocrypt.org; type=x; key= + mQENBFhVGA8BCADK+qTRkAfax0LtJ6RiyxzuAFyIohBTwvtcOM2sd/tRmWq1eyNif5AGDnc1+b6X + zJ6l3BXiYM/8qXU/F04UA5BP05SgIqXjqT5I13blrydjKtUbZFchK7lJU7cyDbar+TH70DZURSQm + MusCj0+fdx6hx8y4LSOM68rjwVeq7JXAPU78QQsYgMrbtkf5mZWUquDdb7tEoxU+PcNifvtvuHF2 + ILv09a4Fi8thJG4i/3LxMFtmMLIiZWLfk5KpXAKrOy436e1LCm3vesALcihPNppb803dgBqpvvEE + 9W7sg5NUy3P8+fTEuvI8HYYd+lEvYe2ojm4HVTts4YFHmzaGVzHLABEBAAG0HVRoaXMgc2hvdWxk + IG5ldmVyIGJlIGltcG9ydGVkiQFOBBMBCAA4FiEE0uuMX0KSMgVBfC/25MZusLe5gWMFAlhVGA8C + GwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQ5MZusLe5gWN9Hwf+PpLCCV7TiGc1nqIxLMTs + O84PVLSQZB642/QhLoMYXQ5iqty5H2FqGuK+uWLCnM+yIMDkcJC3ayWfa06fs3JOipVKlMh8hHnU + 6/FHJB+3eZrc4lhh5B67Vi8Xg43pTP+I9ct/PlbHvD9kYw+DpcmCz0XILhaUP0R1oQ6M5KI49uLg + LAdNczcEtcw3A/hZ5ZTUe3o3gav0XDBXFCgGjkI+CaMjKb/HjgNM9YsrGxUxH1RFMYqTfrmCklHD + EboQc1Qtzi5rIwzVR3zSryve9KHH75TCfDApghwUBKSLNh374hjTFj5v5kPAxG3njX6EOqHS/UVX + Mn5aEVn0n6S1y+DJZLkBDQRYVRgRAQgAzQgD/CluB1wuBeI8qaqmIxG8epHCPstQ4kee6FuFWi3F + Lqtyk1R9tB4UL40gpEkpzB+qYms/zs9SeicuNcXoXA4bMcNGDFz3mZ1d9qG2izgC19e9p50oXiMY + cr8GM1Qcb77dmxlk829cBpr+X7NDKJy9VMGsqNYukgFDnNIzty0oMdCLSzpqi3UtXtCGYDqIiltU + aT8XdMAvddr6Scgpkz3wrqi/bVagc+q4IdKL0r8iL7o3EnTf/5Dc2XUaCFJLCa3Rk6oat5kTWjan + sp/K5k/VzSDcESji8n6xl0OzD2okhmX8iJZg1hhyI8hNmtW3boe51Hkkdlj+wC8Y2Fgh4QARAQAB + iQE2BBgBCAAgFiEE0uuMX0KSMgVBfC/25MZusLe5gWMFAlhVGBECGwwACgkQ5MZusLe5gWOn/Qf/ + aeV7CqZW/YN4/LhXjJG7i+iDJYv/9Lr12dvgjO/sOlmDPHkEzXPMLKalm0biMPN7E1woQzcKt7Qy + eF/CRcVKK1TM6wdClOj2jErnWyx85/uZfnG9QRD41rhInk891A8LGebPZ6DJeJR/uwzMniEgNnKN + AMuGy95ckwlM3AfwzsKPTUUFnBAmSwWfMLRxjZPNefeo1Ic8mMRAT3d5sfDUx/4wm8tyiNLuOSkm + Ej6ONYpESD2sJGMo3ZY96pkzir7ZH++4mH6PwZg1ZT2nO+0PtaB9DHRGfBrzH85d4aLFZD9txx3p + ewabrNpYI/cJu9hUTaTM7wZaG5kmfStwihKYUg== +Date: Sat, 17 Dec 2016 10:51:48 +0100 +Message-ID: +MIME-Version: 1.0 +Content-Type: text/plain + +This message contains an INBOME header that claims to be of type "x", +which is not a specified type. + +An agent capable of INBOME level 0 should reject this inbome header +because of this type. (it should not try to parse it as a header). From 99dea3a466e2f6544e4a21341aad36f81ae2f22f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 28 Jun 2017 15:41:33 +0200 Subject: [PATCH 03/11] import keys from autocrypt headers --- .../fsck/k9/crypto/AutocryptOperations.java | 9 +-- .../fsck/k9/message/PgpMessageBuilder.java | 3 +- .../k9/ui/crypto/MessageCryptoHelper.java | 56 ++++++++++++++++--- 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java b/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java index e0412b90a..57f907d64 100644 --- a/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java +++ b/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java @@ -11,6 +11,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; +import com.fsck.k9.mail.Message; import com.fsck.k9.mail.internet.MimeMessage; import com.fsck.k9.mail.internet.MimeUtility; import okio.ByteString; @@ -30,7 +31,7 @@ public class AutocryptOperations { } - private boolean addAutocryptPeerUpdateToIntentIfPresent(MimeMessage currentMessage, Intent intent) { + public boolean addAutocryptPeerUpdateToIntentIfPresent(Message currentMessage, Intent intent) { AutocryptHeader autocryptHeader = getValidAutocryptHeader(currentMessage); if (autocryptHeader == null) { return false; @@ -51,7 +52,7 @@ public class AutocryptOperations { return true; } - public void processCleartextMessage(OpenPgpApi openPgpApi, MimeMessage currentMessage) { + public void processCleartextMessage(OpenPgpApi openPgpApi, Message currentMessage) { Intent intent = new Intent(OpenPgpApi.ACTION_UPDATE_AUTOCRYPT_PEER); boolean hasInlineKeyData = addAutocryptPeerUpdateToIntentIfPresent(currentMessage, intent); if (hasInlineKeyData) { @@ -61,7 +62,7 @@ public class AutocryptOperations { @Nullable @VisibleForTesting - AutocryptHeader getValidAutocryptHeader(MimeMessage currentMessage) { + AutocryptHeader getValidAutocryptHeader(Message currentMessage) { String[] headers = currentMessage.getHeader(AUTOCRYPT_HEADER); ArrayList autocryptHeaders = parseAllAutocryptHeaders(headers); @@ -126,7 +127,7 @@ public class AutocryptOperations { return false; } - public boolean hasAutocryptHeader(MimeMessage currentMessage) { + public boolean hasAutocryptHeader(Message currentMessage) { return currentMessage.getHeader(AUTOCRYPT_HEADER).length > 0; } diff --git a/k9mail/src/main/java/com/fsck/k9/message/PgpMessageBuilder.java b/k9mail/src/main/java/com/fsck/k9/message/PgpMessageBuilder.java index 8f4254a5a..20e84ff8c 100644 --- a/k9mail/src/main/java/com/fsck/k9/message/PgpMessageBuilder.java +++ b/k9mail/src/main/java/com/fsck/k9/message/PgpMessageBuilder.java @@ -11,10 +11,8 @@ import android.content.Intent; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; -import timber.log.Timber; import com.fsck.k9.Globals; -import com.fsck.k9.K9; import com.fsck.k9.activity.compose.ComposeCryptoStatus; import com.fsck.k9.mail.Body; import com.fsck.k9.mail.BodyPart; @@ -36,6 +34,7 @@ import org.apache.james.mime4j.util.MimeUtil; import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSource; +import timber.log.Timber; public class PgpMessageBuilder extends MessageBuilder { diff --git a/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java b/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java index 59550fd95..7b58fc79a 100644 --- a/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java +++ b/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java @@ -17,6 +17,9 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; +import com.fsck.k9.crypto.AutocryptOperations; +import timber.log.Timber; + import com.fsck.k9.K9; import com.fsck.k9.crypto.MessageDecryptVerifier; import com.fsck.k9.mail.Address; @@ -62,6 +65,7 @@ public class MessageCryptoHelper { private final Context context; private final String openPgpProviderPackage; + private final AutocryptOperations autocryptOperations; private final Object callbackLock = new Object(); private final Deque partsToDecryptOrVerify = new ArrayDeque<>(); @@ -79,6 +83,7 @@ public class MessageCryptoHelper { private Intent currentCryptoResult; private Intent userInteractionResultIntent; private boolean secondPassStarted; + private boolean thirdPassStarted; private CancelableBackgroundOperation cancelableBackgroundOperation; private boolean isCancelled; @@ -95,6 +100,7 @@ public class MessageCryptoHelper { } this.openPgpApiFactory = openPgpApiFactory; + autocryptOperations = new AutocryptOperations(); openPgpProviderPackage = K9.getOpenPgpProvider(); } @@ -125,6 +131,8 @@ public class MessageCryptoHelper { } private void runSecondPass() { + secondPassStarted = true; + List signedParts = MessageDecryptVerifier.findSignedParts(currentMessage, messageAnnotations); processFoundSignedParts(signedParts); @@ -134,6 +142,19 @@ public class MessageCryptoHelper { decryptOrVerifyNextPart(); } + private void runThirdPass() { + thirdPassStarted = true; + + if (messageAnnotations.isEmpty()) { + if (autocryptOperations.hasAutocryptHeader(currentMessage)) { + CryptoPart cryptoPart = new CryptoPart(CryptoPartType.PLAIN_AUTOCRYPT, currentMessage); + partsToDecryptOrVerify.add(cryptoPart); + } + } + + decryptOrVerifyNextPart(); + } + private void processFoundEncryptedParts(List foundParts) { for (Part part : foundParts) { if (!MessageHelper.isCompletePartAvailable(part)) { @@ -194,7 +215,7 @@ public class MessageCryptoHelper { } if (partsToDecryptOrVerify.isEmpty()) { - runSecondPassOrReturnResultToFragment(); + runNextPassOrReturnResultToFragment(); return; } @@ -239,19 +260,22 @@ public class MessageCryptoHelper { Intent decryptIntent = userInteractionResultIntent; userInteractionResultIntent = null; if (decryptIntent == null) { - decryptIntent = getDecryptionIntent(); + decryptIntent = getDecryptVerifyIntent(); } decryptVerify(decryptIntent); } @NonNull - private Intent getDecryptionIntent() { + private Intent getDecryptVerifyIntent() { Intent decryptIntent = new Intent(OpenPgpApi.ACTION_DECRYPT_VERIFY); Address[] from = currentMessage.getFrom(); if (from.length > 0) { decryptIntent.putExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS, from[0].getAddress()); + // we add this here independently of the autocrypt peer update, to allow picking up signing keys as gossip + decryptIntent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID, from[0].getAddress()); } + autocryptOperations.addAutocryptPeerUpdateToIntentIfPresent(currentMessage, decryptIntent); decryptIntent.putExtra(OpenPgpApi.EXTRA_SUPPORT_OVERRIDE_CRYPTO_WARNING, true); decryptIntent.putExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT, cachedDecryptionResult); @@ -275,6 +299,10 @@ public class MessageCryptoHelper { callAsyncInlineOperation(intent); return; } + case PLAIN_AUTOCRYPT: { + callAsyncParseAutocryptHeaderOperation(); + return; + } } throw new IllegalStateException("Unknown crypto part type: " + cryptoPartType); @@ -285,6 +313,12 @@ public class MessageCryptoHelper { } } + private void callAsyncParseAutocryptHeaderOperation() { + // TODO make actually async (then callback to onCryptoFinished) + autocryptOperations.processCleartextMessage(openPgpApi, (Message) currentCryptoPart.part); + onCryptoFinished(); + } + private void callAsyncInlineOperation(Intent intent) throws IOException { OpenPgpDataSource dataSource = getDataSourceForEncryptedOrInlineData(); OpenPgpDataSink dataSink = getDataSinkForDecryptedInlineData(); @@ -600,13 +634,16 @@ public class MessageCryptoHelper { decryptOrVerifyNextPart(); } - private void runSecondPassOrReturnResultToFragment() { - if (secondPassStarted) { - callbackReturnResult(); + private void runNextPassOrReturnResultToFragment() { + if (!secondPassStarted) { + runSecondPass(); return; } - secondPassStarted = true; - runSecondPass(); + if (!thirdPassStarted) { + runThirdPass(); + return; + } + callbackReturnResult(); } private void cleanupAfterProcessingFinished() { @@ -699,7 +736,8 @@ public class MessageCryptoHelper { private enum CryptoPartType { PGP_INLINE, PGP_ENCRYPTED, - PGP_SIGNED + PGP_SIGNED, + PLAIN_AUTOCRYPT } @Nullable From 515fc21c77653c6be5c0c2ab4c74e433d4ebd8b5 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 28 Jun 2017 16:07:08 +0200 Subject: [PATCH 04/11] update parameter names, and handle prefer-encrypt=mutual --- .../fsck/k9/crypto/AutocryptOperations.java | 26 ++++++++++++++----- .../openpgp/AutocryptPeerUpdate.java | 4 +-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java b/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java index 57f907d64..692cd0aba 100644 --- a/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java +++ b/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java @@ -21,10 +21,16 @@ import timber.log.Timber; public class AutocryptOperations { - private static final String AUTOCRYPT_PARAM_KEY_DATA = "key"; - private static final String AUTOCRYPT_PARAM_TO = "to"; private static final String AUTOCRYPT_HEADER = "Autocrypt"; + + private static final String AUTOCRYPT_PARAM_TO = "addr"; + private static final String AUTOCRYPT_PARAM_KEY_DATA = "key"; + private static final String AUTOCRYPT_PARAM_TYPE = "type"; + private static final String AUTOCRYPT_TYPE_1 = "1"; + + private static final String AUTOCRYPT_PARAM_PREFER_ENCRYPT = "prefer-encrypt"; + private static final String AUTOCRYPT_PREFER_ENCRYPT_MUTUAL = "mutual"; public AutocryptOperations() { @@ -46,7 +52,8 @@ public class AutocryptOperations { Date internalDate = currentMessage.getInternalDate(); Date effectiveDate = messageDate.before(internalDate) ? messageDate : internalDate; - AutocryptPeerUpdate data = AutocryptPeerUpdate.createAutocryptPeerUpdate(autocryptHeader.keyData, effectiveDate); + AutocryptPeerUpdate data = AutocryptPeerUpdate.createAutocryptPeerUpdate( + autocryptHeader.keyData, effectiveDate, autocryptHeader.isPreferEncryptMutual); intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID, messageFromAddress); intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE, data); return true; @@ -87,7 +94,7 @@ public class AutocryptOperations { Map parameters = MimeUtility.getAllHeaderParameters(headerValue); String type = parameters.remove(AUTOCRYPT_PARAM_TYPE); - if (type != null && !type.equals("p")) { + if (type != null && !type.equals(AUTOCRYPT_TYPE_1)) { Timber.e("autocrypt: unsupported type parameter %s", type); return null; } @@ -110,12 +117,17 @@ public class AutocryptOperations { return null; } + boolean isPreferEncryptMutual = false; + String preferEncrypt = parameters.remove(AUTOCRYPT_PARAM_PREFER_ENCRYPT); + if (AUTOCRYPT_PREFER_ENCRYPT_MUTUAL.equalsIgnoreCase(preferEncrypt)) { + isPreferEncryptMutual = true; + } if (hasCriticalParameters(parameters)) { return null; } - return new AutocryptHeader(parameters, to, byteString.toByteArray()); + return new AutocryptHeader(parameters, to, byteString.toByteArray(), isPreferEncryptMutual); } private boolean hasCriticalParameters(Map parameters) { @@ -136,11 +148,13 @@ public class AutocryptOperations { final byte[] keyData; final String to; final Map parameters; + final boolean isPreferEncryptMutual; - private AutocryptHeader(Map parameters, String to, byte[] keyData) { + private AutocryptHeader(Map parameters, String to, byte[] keyData, boolean isPreferEncryptMutual) { this.parameters = parameters; this.to = to; this.keyData = keyData; + this.isPreferEncryptMutual = isPreferEncryptMutual; } } } diff --git a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java index 5b723547d..256d37f6f 100644 --- a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java +++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java @@ -51,8 +51,8 @@ public class AutocryptPeerUpdate implements Parcelable { } - public static AutocryptPeerUpdate createAutocryptPeerUpdate(byte[] keyData, Date timestamp) { - return new AutocryptPeerUpdate(keyData, timestamp, PreferEncrypt.NOPREFERENCE); + public static AutocryptPeerUpdate createAutocryptPeerUpdate(byte[] keyData, Date timestamp, boolean isMutual) { + return new AutocryptPeerUpdate(keyData, timestamp, isMutual ? PreferEncrypt.MUTUAL : PreferEncrypt.NOPREFERENCE); } public byte[] getKeyData() { From a7aaefe4046328698daa6328b3e09defa20aff6b Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 28 Jun 2017 16:17:52 +0200 Subject: [PATCH 05/11] fix autocrypt tests (they were still INBOME) --- .../main/java/com/fsck/k9/crypto/AutocryptOperations.java | 8 ++++---- .../java/com/fsck/k9/crypto/AutocryptOperationsTest.java | 6 +++--- .../test/resources/autocrypt/rsa2048-broken-base64.eml | 2 +- .../test/resources/autocrypt/rsa2048-explicit-type.eml | 2 +- k9mail/src/test/resources/autocrypt/rsa2048-simple.eml | 2 +- .../test/resources/autocrypt/rsa2048-unknown-critical.eml | 2 +- .../resources/autocrypt/rsa2048-unknown-non-critical.eml | 2 +- k9mail/src/test/resources/autocrypt/unknown-type.eml | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java b/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java index 692cd0aba..12b890605 100644 --- a/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java +++ b/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java @@ -44,7 +44,7 @@ public class AutocryptOperations { } String messageFromAddress = currentMessage.getFrom()[0].getAddress(); - if (!autocryptHeader.to.equalsIgnoreCase(messageFromAddress)) { + if (!autocryptHeader.addr.equalsIgnoreCase(messageFromAddress)) { return false; } @@ -146,13 +146,13 @@ public class AutocryptOperations { @VisibleForTesting class AutocryptHeader { final byte[] keyData; - final String to; + final String addr; final Map parameters; final boolean isPreferEncryptMutual; - private AutocryptHeader(Map parameters, String to, byte[] keyData, boolean isPreferEncryptMutual) { + private AutocryptHeader(Map parameters, String addr, byte[] keyData, boolean isPreferEncryptMutual) { this.parameters = parameters; - this.to = to; + this.addr = addr; this.keyData = keyData; this.isPreferEncryptMutual = isPreferEncryptMutual; } diff --git a/k9mail/src/test/java/com/fsck/k9/crypto/AutocryptOperationsTest.java b/k9mail/src/test/java/com/fsck/k9/crypto/AutocryptOperationsTest.java index af044a219..e9e11981b 100644 --- a/k9mail/src/test/java/com/fsck/k9/crypto/AutocryptOperationsTest.java +++ b/k9mail/src/test/java/com/fsck/k9/crypto/AutocryptOperationsTest.java @@ -59,7 +59,7 @@ public class AutocryptOperationsTest { AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); assertNotNull(autocryptHeader); - assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.to); + assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr); assertEquals(0, autocryptHeader.parameters.size()); assertEquals(1225, autocryptHeader.keyData.length); } @@ -71,7 +71,7 @@ public class AutocryptOperationsTest { AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); assertNotNull(autocryptHeader); - assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.to); + assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr); assertEquals(0, autocryptHeader.parameters.size()); } @@ -100,7 +100,7 @@ public class AutocryptOperationsTest { AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); assertNotNull(autocryptHeader); - assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.to); + assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr); assertEquals(1, autocryptHeader.parameters.size()); assertEquals("ignore", autocryptHeader.parameters.get("_monkey")); } diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-broken-base64.eml b/k9mail/src/test/resources/autocrypt/rsa2048-broken-base64.eml index 0eca32499..e61e7f348 100644 --- a/k9mail/src/test/resources/autocrypt/rsa2048-broken-base64.eml +++ b/k9mail/src/test/resources/autocrypt/rsa2048-broken-base64.eml @@ -1,7 +1,7 @@ From: Alice To: Bob Subject: an INBOME RSA test -INBOME: to=alice@testsuite.autocrypt.org; key= +Autocrypt: addr=alice@testsuite.autocrypt.org; key= m!@#$%hVF+ABCADu17FBUgA3mCemeKbNaBTyWe3VGxjbu7fUyHgdLK7i3tnd7IRtxQy/AEN2t6Vq 0/xeZEAKYRInsHI/HjvmhqPeWFzipk71jRQ02WUY1pZytFjYNIrTdMk4eLYdC1N0go83PU33V4R8 fc2fWHD8N5JPsDH2xOB6WNWkMPxgMbtGIa0QTx7TINhDif4/1/VcrX3wz1gZ6xYI+sujbC54iBZo diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-explicit-type.eml b/k9mail/src/test/resources/autocrypt/rsa2048-explicit-type.eml index c7d8c941c..1a2c1141e 100644 --- a/k9mail/src/test/resources/autocrypt/rsa2048-explicit-type.eml +++ b/k9mail/src/test/resources/autocrypt/rsa2048-explicit-type.eml @@ -1,7 +1,7 @@ From: Alice To: Bob Subject: inbome rsa2048 with correct type -INBOME: to=alice@testsuite.autocrypt.org; type=p; key= +Autocrypt: addr=alice@testsuite.autocrypt.org; type=1; key= mQENBFhVF+ABCADu17FBUgA3mCemeKbNaBTyWe3VGxjbu7fUyHgdLK7i3tnd7IRtxQy/AEN2t6Vq 0/xeZEAKYRInsHI/HjvmhqPeWFzipk71jRQ02WUY1pZytFjYNIrTdMk4eLYdC1N0go83PU33V4R8 fc2fWHD8N5JPsDH2xOB6WNWkMPxgMbtGIa0QTx7TINhDif4/1/VcrX3wz1gZ6xYI+sujbC54iBZo diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-simple.eml b/k9mail/src/test/resources/autocrypt/rsa2048-simple.eml index 96440dbdc..512506209 100644 --- a/k9mail/src/test/resources/autocrypt/rsa2048-simple.eml +++ b/k9mail/src/test/resources/autocrypt/rsa2048-simple.eml @@ -1,7 +1,7 @@ From: Alice To: Bob Subject: an INBOME RSA test -INBOME: to=alice@testsuite.autocrypt.org; key= +Autocrypt: addr=alice@testsuite.autocrypt.org; key= mQENBFhVF+ABCADu17FBUgA3mCemeKbNaBTyWe3VGxjbu7fUyHgdLK7i3tnd7IRtxQy/AEN2t6Vq 0/xeZEAKYRInsHI/HjvmhqPeWFzipk71jRQ02WUY1pZytFjYNIrTdMk4eLYdC1N0go83PU33V4R8 fc2fWHD8N5JPsDH2xOB6WNWkMPxgMbtGIa0QTx7TINhDif4/1/VcrX3wz1gZ6xYI+sujbC54iBZo diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-unknown-critical.eml b/k9mail/src/test/resources/autocrypt/rsa2048-unknown-critical.eml index 32acf0d88..7f06f9e27 100644 --- a/k9mail/src/test/resources/autocrypt/rsa2048-unknown-critical.eml +++ b/k9mail/src/test/resources/autocrypt/rsa2048-unknown-critical.eml @@ -1,7 +1,7 @@ From: Alice To: Bob Subject: another inbome with rsa2048 but with an unknown critical attribute -INBOME: to=alice@testsuite.autocrypt.org; danger=do-not-use; key= +Autocrypt: addr=alice@testsuite.autocrypt.org; danger=do-not-use; key= mQENBFhVGA8BCADK+qTRkAfax0LtJ6RiyxzuAFyIohBTwvtcOM2sd/tRmWq1eyNif5AGDnc1+b6X zJ6l3BXiYM/8qXU/F04UA5BP05SgIqXjqT5I13blrydjKtUbZFchK7lJU7cyDbar+TH70DZURSQm MusCj0+fdx6hx8y4LSOM68rjwVeq7JXAPU78QQsYgMrbtkf5mZWUquDdb7tEoxU+PcNifvtvuHF2 diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-unknown-non-critical.eml b/k9mail/src/test/resources/autocrypt/rsa2048-unknown-non-critical.eml index af1b5d8fa..3d91cc250 100644 --- a/k9mail/src/test/resources/autocrypt/rsa2048-unknown-non-critical.eml +++ b/k9mail/src/test/resources/autocrypt/rsa2048-unknown-non-critical.eml @@ -1,7 +1,7 @@ From: Alice To: Bob Subject: inbome: rsa 2048 with unknown non-critical attribute -INBOME: to=alice@testsuite.autocrypt.org; _monkey=ignore; key= +Autocrypt: addr=alice@testsuite.autocrypt.org; _monkey=ignore; key= mQENBFhVF+ABCADu17FBUgA3mCemeKbNaBTyWe3VGxjbu7fUyHgdLK7i3tnd7IRtxQy/AEN2t6Vq 0/xeZEAKYRInsHI/HjvmhqPeWFzipk71jRQ02WUY1pZytFjYNIrTdMk4eLYdC1N0go83PU33V4R8 fc2fWHD8N5JPsDH2xOB6WNWkMPxgMbtGIa0QTx7TINhDif4/1/VcrX3wz1gZ6xYI+sujbC54iBZo diff --git a/k9mail/src/test/resources/autocrypt/unknown-type.eml b/k9mail/src/test/resources/autocrypt/unknown-type.eml index 7a79a26aa..7b423cfdc 100644 --- a/k9mail/src/test/resources/autocrypt/unknown-type.eml +++ b/k9mail/src/test/resources/autocrypt/unknown-type.eml @@ -1,7 +1,7 @@ From: Alice To: Bob Subject: INBOME with invalid type attribute -INBOME: to=alice@testsuite.autocrypt.org; type=x; key= +Autocrypt: addr=alice@testsuite.autocrypt.org; type=x; key= mQENBFhVGA8BCADK+qTRkAfax0LtJ6RiyxzuAFyIohBTwvtcOM2sd/tRmWq1eyNif5AGDnc1+b6X zJ6l3BXiYM/8qXU/F04UA5BP05SgIqXjqT5I13blrydjKtUbZFchK7lJU7cyDbar+TH70DZURSQm MusCj0+fdx6hx8y4LSOM68rjwVeq7JXAPU78QQsYgMrbtkf5mZWUquDdb7tEoxU+PcNifvtvuHF2 From 2e7c6cf5e84817dbeb335dd57680fd325e55872f Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Mon, 21 Aug 2017 20:30:32 +0200 Subject: [PATCH 06/11] nicer MessageCryptoHelper structure, and handle plain autocrypt asynchronously --- .../fsck/k9/crypto/AutocryptOperations.java | 10 -- .../k9/ui/crypto/MessageCryptoHelper.java | 137 ++++++++++-------- 2 files changed, 75 insertions(+), 72 deletions(-) diff --git a/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java b/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java index 12b890605..03bb9bd4c 100644 --- a/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java +++ b/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java @@ -1,7 +1,6 @@ package com.fsck.k9.crypto; -import java.io.InputStream; import java.util.ArrayList; import java.util.Date; import java.util.Map; @@ -12,7 +11,6 @@ import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import com.fsck.k9.mail.Message; -import com.fsck.k9.mail.internet.MimeMessage; import com.fsck.k9.mail.internet.MimeUtility; import okio.ByteString; import org.openintents.openpgp.AutocryptPeerUpdate; @@ -59,14 +57,6 @@ public class AutocryptOperations { return true; } - public void processCleartextMessage(OpenPgpApi openPgpApi, Message currentMessage) { - Intent intent = new Intent(OpenPgpApi.ACTION_UPDATE_AUTOCRYPT_PEER); - boolean hasInlineKeyData = addAutocryptPeerUpdateToIntentIfPresent(currentMessage, intent); - if (hasInlineKeyData) { - openPgpApi.executeApi(intent, (InputStream) null, null); - } - } - @Nullable @VisibleForTesting AutocryptHeader getValidAutocryptHeader(Message currentMessage) { diff --git a/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java b/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java index 7b58fc79a..b42f70f72 100644 --- a/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java +++ b/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java @@ -17,10 +17,8 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; -import com.fsck.k9.crypto.AutocryptOperations; -import timber.log.Timber; - import com.fsck.k9.K9; +import com.fsck.k9.crypto.AutocryptOperations; import com.fsck.k9.crypto.MessageDecryptVerifier; import com.fsck.k9.mail.Address; import com.fsck.k9.mail.Body; @@ -48,6 +46,7 @@ import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpApi.CancelableBackgroundOperation; +import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback; import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpSinkResultCallback; import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSink; import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSource; @@ -67,7 +66,7 @@ public class MessageCryptoHelper { private final String openPgpProviderPackage; private final AutocryptOperations autocryptOperations; private final Object callbackLock = new Object(); - private final Deque partsToDecryptOrVerify = new ArrayDeque<>(); + private final Deque partsToProcess = new ArrayDeque<>(); @Nullable private MessageCryptoCallback callback; @@ -82,6 +81,7 @@ public class MessageCryptoHelper { private CryptoPart currentCryptoPart; private Intent currentCryptoResult; private Intent userInteractionResultIntent; + private boolean firstPassStarted; private boolean secondPassStarted; private boolean thirdPassStarted; private CancelableBackgroundOperation cancelableBackgroundOperation; @@ -120,17 +120,17 @@ public class MessageCryptoHelper { this.cachedDecryptionResult = cachedDecryptionResult; this.callback = callback; - runFirstPass(); + nextStep(); } - private void runFirstPass() { + private void findPartsForFirstPass() { + firstPassStarted = true; + List encryptedParts = MessageDecryptVerifier.findEncryptedParts(currentMessage); processFoundEncryptedParts(encryptedParts); - - decryptOrVerifyNextPart(); } - private void runSecondPass() { + private void findPartsForSecondPass() { secondPassStarted = true; List signedParts = MessageDecryptVerifier.findSignedParts(currentMessage, messageAnnotations); @@ -138,21 +138,16 @@ public class MessageCryptoHelper { List inlineParts = MessageDecryptVerifier.findPgpInlineParts(currentMessage); addFoundInlinePgpParts(inlineParts); - - decryptOrVerifyNextPart(); } - private void runThirdPass() { + private void findPartsForThirdPass() { thirdPassStarted = true; - if (messageAnnotations.isEmpty()) { - if (autocryptOperations.hasAutocryptHeader(currentMessage)) { - CryptoPart cryptoPart = new CryptoPart(CryptoPartType.PLAIN_AUTOCRYPT, currentMessage); - partsToDecryptOrVerify.add(cryptoPart); - } + boolean noOtherCryptoPerformed = messageAnnotations.isEmpty(); + if (noOtherCryptoPerformed && autocryptOperations.hasAutocryptHeader(currentMessage)) { + CryptoPart cryptoPart = new CryptoPart(CryptoPartType.PLAIN_AUTOCRYPT, currentMessage); + partsToProcess.add(cryptoPart); } - - decryptOrVerifyNextPart(); } private void processFoundEncryptedParts(List foundParts) { @@ -163,7 +158,7 @@ public class MessageCryptoHelper { } if (MessageDecryptVerifier.isPgpMimeEncryptedOrSignedPart(part)) { CryptoPart cryptoPart = new CryptoPart(CryptoPartType.PGP_ENCRYPTED, part); - partsToDecryptOrVerify.add(cryptoPart); + partsToProcess.add(cryptoPart); continue; } addErrorAnnotation(part, CryptoError.ENCRYPTED_BUT_UNSUPPORTED, MessageHelper.createEmptyPart()); @@ -179,7 +174,7 @@ public class MessageCryptoHelper { } if (MessageDecryptVerifier.isPgpMimeEncryptedOrSignedPart(part)) { CryptoPart cryptoPart = new CryptoPart(CryptoPartType.PGP_SIGNED, part); - partsToDecryptOrVerify.add(cryptoPart); + partsToProcess.add(cryptoPart); continue; } MimeBodyPart replacementPart = getMultipartSignedContentPartIfAvailable(part); @@ -205,29 +200,34 @@ public class MessageCryptoHelper { } CryptoPart cryptoPart = new CryptoPart(CryptoPartType.PGP_INLINE, part); - partsToDecryptOrVerify.add(cryptoPart); + partsToProcess.add(cryptoPart); } } - private void decryptOrVerifyNextPart() { + private void nextStep() { if (isCancelled) { return; } - if (partsToDecryptOrVerify.isEmpty()) { - runNextPassOrReturnResultToFragment(); + while (partsToProcess.isEmpty()) { + boolean hadNextPass = findPartsForNextPass(); + + if (!hadNextPass) { + callbackReturnResult(); + return; + } + } + + if (!isBoundToCryptoProviderService()) { + connectToCryptoProviderService(); return; } - CryptoPart cryptoPart = partsToDecryptOrVerify.peekFirst(); - startDecryptingOrVerifyingPart(cryptoPart); - } - - private void startDecryptingOrVerifyingPart(CryptoPart cryptoPart) { - if (!isBoundToCryptoProviderService()) { - connectToCryptoProviderService(); + currentCryptoPart = partsToProcess.peekFirst(); + if (currentCryptoPart.type == CryptoPartType.PLAIN_AUTOCRYPT) { + processAutocryptHeaderForCurrentPart(); } else { - decryptOrVerifyPart(cryptoPart); + decryptOrVerifyCurrentPart(); } } @@ -243,7 +243,7 @@ public class MessageCryptoHelper { public void onBound(IOpenPgpService2 service) { openPgpApi = openPgpApiFactory.createOpenPgpApi(context, service); - decryptOrVerifyNextPart(); + nextStep(); } @Override @@ -255,14 +255,13 @@ public class MessageCryptoHelper { openPgpServiceConnection.bindToService(); } - private void decryptOrVerifyPart(CryptoPart cryptoPart) { - currentCryptoPart = cryptoPart; - Intent decryptIntent = userInteractionResultIntent; + private void decryptOrVerifyCurrentPart() { + Intent apiIntent = userInteractionResultIntent; userInteractionResultIntent = null; - if (decryptIntent == null) { - decryptIntent = getDecryptVerifyIntent(); + if (apiIntent == null) { + apiIntent = getDecryptVerifyIntent(); } - decryptVerify(decryptIntent); + decryptVerify(apiIntent); } @NonNull @@ -283,26 +282,24 @@ public class MessageCryptoHelper { return decryptIntent; } - private void decryptVerify(Intent intent) { + private void decryptVerify(Intent apiIntent) { try { CryptoPartType cryptoPartType = currentCryptoPart.type; switch (cryptoPartType) { case PGP_SIGNED: { - callAsyncDetachedVerify(intent); + callAsyncDetachedVerify(apiIntent); return; } case PGP_ENCRYPTED: { - callAsyncDecrypt(intent); + callAsyncDecrypt(apiIntent); return; } case PGP_INLINE: { - callAsyncInlineOperation(intent); - return; - } - case PLAIN_AUTOCRYPT: { - callAsyncParseAutocryptHeaderOperation(); + callAsyncInlineOperation(apiIntent); return; } + case PLAIN_AUTOCRYPT: + throw new IllegalStateException("This part type must have been handled previously!"); } throw new IllegalStateException("Unknown crypto part type: " + cryptoPartType); @@ -313,9 +310,20 @@ public class MessageCryptoHelper { } } - private void callAsyncParseAutocryptHeaderOperation() { - // TODO make actually async (then callback to onCryptoFinished) - autocryptOperations.processCleartextMessage(openPgpApi, (Message) currentCryptoPart.part); + private void processAutocryptHeaderForCurrentPart() { + Intent intent = new Intent(OpenPgpApi.ACTION_UPDATE_AUTOCRYPT_PEER); + boolean hasInlineKeyData = autocryptOperations.addAutocryptPeerUpdateToIntentIfPresent( + (Message) currentCryptoPart.part, intent); + if (hasInlineKeyData) { + Timber.d("Passing autocrypt data from plain mail to OpenPGP API"); + // We don't care about the result here, so we just call this fire-and-forget wait to minimize delay + openPgpApi.executeApiAsync(intent, null, null, new IOpenPgpCallback() { + @Override + public void onReturn(Intent result) { + Timber.d("Autocrypt update OK!"); + } + }); + } onCryptoFinished(); } @@ -568,7 +576,7 @@ public class MessageCryptoHelper { } if (resultCode == Activity.RESULT_OK) { userInteractionResultIntent = data; - decryptOrVerifyNextPart(); + nextStep(); } else { onCryptoOperationCanceled(); } @@ -620,34 +628,39 @@ public class MessageCryptoHelper { } private void onCryptoFinished() { - boolean currentPartIsFirstInQueue = partsToDecryptOrVerify.peekFirst() == currentCryptoPart; + boolean currentPartIsFirstInQueue = partsToProcess.peekFirst() == currentCryptoPart; if (!currentPartIsFirstInQueue) { throw new IllegalStateException( "Trying to remove part from queue that is not the currently processed one!"); } if (currentCryptoPart != null) { - partsToDecryptOrVerify.removeFirst(); + partsToProcess.removeFirst(); currentCryptoPart = null; } else { Timber.e(new Throwable(), "Got to onCryptoFinished() with no part in processing!"); } - decryptOrVerifyNextPart(); + nextStep(); } - private void runNextPassOrReturnResultToFragment() { + private boolean findPartsForNextPass() { + if (!firstPassStarted) { + findPartsForFirstPass(); + return true; + } if (!secondPassStarted) { - runSecondPass(); - return; + findPartsForSecondPass(); + return true; } if (!thirdPassStarted) { - runThirdPass(); - return; + findPartsForThirdPass(); + return true; } - callbackReturnResult(); + + return false; } private void cleanupAfterProcessingFinished() { - partsToDecryptOrVerify.clear(); + partsToProcess.clear(); openPgpApi = null; if (openPgpServiceConnection != null) { openPgpServiceConnection.unbindFromService(); From 8f1a892f1f98e47f72573b93a9275b5072dd1c5d Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 27 Aug 2017 23:02:40 +0200 Subject: [PATCH 07/11] change autocrypt param according to updated spec --- .../src/main/java/com/fsck/k9/crypto/AutocryptOperations.java | 2 +- k9mail/src/test/resources/autocrypt/rsa2048-broken-base64.eml | 2 +- k9mail/src/test/resources/autocrypt/rsa2048-explicit-type.eml | 2 +- k9mail/src/test/resources/autocrypt/rsa2048-simple-to-bot.eml | 2 +- k9mail/src/test/resources/autocrypt/rsa2048-simple.eml | 2 +- .../src/test/resources/autocrypt/rsa2048-unknown-critical.eml | 2 +- .../test/resources/autocrypt/rsa2048-unknown-non-critical.eml | 2 +- k9mail/src/test/resources/autocrypt/unknown-type.eml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java b/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java index 03bb9bd4c..ffdd1555a 100644 --- a/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java +++ b/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java @@ -22,7 +22,7 @@ public class AutocryptOperations { private static final String AUTOCRYPT_HEADER = "Autocrypt"; private static final String AUTOCRYPT_PARAM_TO = "addr"; - private static final String AUTOCRYPT_PARAM_KEY_DATA = "key"; + private static final String AUTOCRYPT_PARAM_KEY_DATA = "keydata"; private static final String AUTOCRYPT_PARAM_TYPE = "type"; private static final String AUTOCRYPT_TYPE_1 = "1"; diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-broken-base64.eml b/k9mail/src/test/resources/autocrypt/rsa2048-broken-base64.eml index e61e7f348..b2a984d0a 100644 --- a/k9mail/src/test/resources/autocrypt/rsa2048-broken-base64.eml +++ b/k9mail/src/test/resources/autocrypt/rsa2048-broken-base64.eml @@ -1,7 +1,7 @@ From: Alice To: Bob Subject: an INBOME RSA test -Autocrypt: addr=alice@testsuite.autocrypt.org; key= +Autocrypt: addr=alice@testsuite.autocrypt.org; keydata= m!@#$%hVF+ABCADu17FBUgA3mCemeKbNaBTyWe3VGxjbu7fUyHgdLK7i3tnd7IRtxQy/AEN2t6Vq 0/xeZEAKYRInsHI/HjvmhqPeWFzipk71jRQ02WUY1pZytFjYNIrTdMk4eLYdC1N0go83PU33V4R8 fc2fWHD8N5JPsDH2xOB6WNWkMPxgMbtGIa0QTx7TINhDif4/1/VcrX3wz1gZ6xYI+sujbC54iBZo diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-explicit-type.eml b/k9mail/src/test/resources/autocrypt/rsa2048-explicit-type.eml index 1a2c1141e..72a6b95e6 100644 --- a/k9mail/src/test/resources/autocrypt/rsa2048-explicit-type.eml +++ b/k9mail/src/test/resources/autocrypt/rsa2048-explicit-type.eml @@ -1,7 +1,7 @@ From: Alice To: Bob Subject: inbome rsa2048 with correct type -Autocrypt: addr=alice@testsuite.autocrypt.org; type=1; key= +Autocrypt: addr=alice@testsuite.autocrypt.org; type=1; keydata= mQENBFhVF+ABCADu17FBUgA3mCemeKbNaBTyWe3VGxjbu7fUyHgdLK7i3tnd7IRtxQy/AEN2t6Vq 0/xeZEAKYRInsHI/HjvmhqPeWFzipk71jRQ02WUY1pZytFjYNIrTdMk4eLYdC1N0go83PU33V4R8 fc2fWHD8N5JPsDH2xOB6WNWkMPxgMbtGIa0QTx7TINhDif4/1/VcrX3wz1gZ6xYI+sujbC54iBZo diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-simple-to-bot.eml b/k9mail/src/test/resources/autocrypt/rsa2048-simple-to-bot.eml index 452ea7fc8..09241ab37 100644 --- a/k9mail/src/test/resources/autocrypt/rsa2048-simple-to-bot.eml +++ b/k9mail/src/test/resources/autocrypt/rsa2048-simple-to-bot.eml @@ -1,7 +1,7 @@ From: Alice To: Autocrypt-Bot Subject: an INBOME RSA test -INBOME: to=alice@testsuite.autocrypt.org; key= +INBOME: to=alice@testsuite.autocrypt.org; keydata= mQENBFhVF+ABCADu17FBUgA3mCemeKbNaBTyWe3VGxjbu7fUyHgdLK7i3tnd7IRtxQy/AEN2t6Vq 0/xeZEAKYRInsHI/HjvmhqPeWFzipk71jRQ02WUY1pZytFjYNIrTdMk4eLYdC1N0go83PU33V4R8 fc2fWHD8N5JPsDH2xOB6WNWkMPxgMbtGIa0QTx7TINhDif4/1/VcrX3wz1gZ6xYI+sujbC54iBZo diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-simple.eml b/k9mail/src/test/resources/autocrypt/rsa2048-simple.eml index 512506209..56417bc11 100644 --- a/k9mail/src/test/resources/autocrypt/rsa2048-simple.eml +++ b/k9mail/src/test/resources/autocrypt/rsa2048-simple.eml @@ -1,7 +1,7 @@ From: Alice To: Bob Subject: an INBOME RSA test -Autocrypt: addr=alice@testsuite.autocrypt.org; key= +Autocrypt: addr=alice@testsuite.autocrypt.org; keydata= mQENBFhVF+ABCADu17FBUgA3mCemeKbNaBTyWe3VGxjbu7fUyHgdLK7i3tnd7IRtxQy/AEN2t6Vq 0/xeZEAKYRInsHI/HjvmhqPeWFzipk71jRQ02WUY1pZytFjYNIrTdMk4eLYdC1N0go83PU33V4R8 fc2fWHD8N5JPsDH2xOB6WNWkMPxgMbtGIa0QTx7TINhDif4/1/VcrX3wz1gZ6xYI+sujbC54iBZo diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-unknown-critical.eml b/k9mail/src/test/resources/autocrypt/rsa2048-unknown-critical.eml index 7f06f9e27..05edf2437 100644 --- a/k9mail/src/test/resources/autocrypt/rsa2048-unknown-critical.eml +++ b/k9mail/src/test/resources/autocrypt/rsa2048-unknown-critical.eml @@ -1,7 +1,7 @@ From: Alice To: Bob Subject: another inbome with rsa2048 but with an unknown critical attribute -Autocrypt: addr=alice@testsuite.autocrypt.org; danger=do-not-use; key= +Autocrypt: addr=alice@testsuite.autocrypt.org; danger=do-not-use; keydata= mQENBFhVGA8BCADK+qTRkAfax0LtJ6RiyxzuAFyIohBTwvtcOM2sd/tRmWq1eyNif5AGDnc1+b6X zJ6l3BXiYM/8qXU/F04UA5BP05SgIqXjqT5I13blrydjKtUbZFchK7lJU7cyDbar+TH70DZURSQm MusCj0+fdx6hx8y4LSOM68rjwVeq7JXAPU78QQsYgMrbtkf5mZWUquDdb7tEoxU+PcNifvtvuHF2 diff --git a/k9mail/src/test/resources/autocrypt/rsa2048-unknown-non-critical.eml b/k9mail/src/test/resources/autocrypt/rsa2048-unknown-non-critical.eml index 3d91cc250..f5e046be7 100644 --- a/k9mail/src/test/resources/autocrypt/rsa2048-unknown-non-critical.eml +++ b/k9mail/src/test/resources/autocrypt/rsa2048-unknown-non-critical.eml @@ -1,7 +1,7 @@ From: Alice To: Bob Subject: inbome: rsa 2048 with unknown non-critical attribute -Autocrypt: addr=alice@testsuite.autocrypt.org; _monkey=ignore; key= +Autocrypt: addr=alice@testsuite.autocrypt.org; _monkey=ignore; keydata= mQENBFhVF+ABCADu17FBUgA3mCemeKbNaBTyWe3VGxjbu7fUyHgdLK7i3tnd7IRtxQy/AEN2t6Vq 0/xeZEAKYRInsHI/HjvmhqPeWFzipk71jRQ02WUY1pZytFjYNIrTdMk4eLYdC1N0go83PU33V4R8 fc2fWHD8N5JPsDH2xOB6WNWkMPxgMbtGIa0QTx7TINhDif4/1/VcrX3wz1gZ6xYI+sujbC54iBZo diff --git a/k9mail/src/test/resources/autocrypt/unknown-type.eml b/k9mail/src/test/resources/autocrypt/unknown-type.eml index 7b423cfdc..bf79de092 100644 --- a/k9mail/src/test/resources/autocrypt/unknown-type.eml +++ b/k9mail/src/test/resources/autocrypt/unknown-type.eml @@ -1,7 +1,7 @@ From: Alice To: Bob Subject: INBOME with invalid type attribute -Autocrypt: addr=alice@testsuite.autocrypt.org; type=x; key= +Autocrypt: addr=alice@testsuite.autocrypt.org; type=x; keydata= mQENBFhVGA8BCADK+qTRkAfax0LtJ6RiyxzuAFyIohBTwvtcOM2sd/tRmWq1eyNif5AGDnc1+b6X zJ6l3BXiYM/8qXU/F04UA5BP05SgIqXjqT5I13blrydjKtUbZFchK7lJU7cyDbar+TH70DZURSQm MusCj0+fdx6hx8y4LSOM68rjwVeq7JXAPU78QQsYgMrbtkf5mZWUquDdb7tEoxU+PcNifvtvuHF2 From 0fe68498e0b09569be262716e3922ca90b62448e Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 27 Aug 2017 23:05:06 +0200 Subject: [PATCH 08/11] change method name for less redundancy --- .../src/main/java/com/fsck/k9/crypto/AutocryptOperations.java | 2 +- .../main/java/org/openintents/openpgp/AutocryptPeerUpdate.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java b/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java index ffdd1555a..54e3afd07 100644 --- a/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java +++ b/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java @@ -50,7 +50,7 @@ public class AutocryptOperations { Date internalDate = currentMessage.getInternalDate(); Date effectiveDate = messageDate.before(internalDate) ? messageDate : internalDate; - AutocryptPeerUpdate data = AutocryptPeerUpdate.createAutocryptPeerUpdate( + AutocryptPeerUpdate data = AutocryptPeerUpdate.create( autocryptHeader.keyData, effectiveDate, autocryptHeader.isPreferEncryptMutual); intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID, messageFromAddress); intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE, data); diff --git a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java index 256d37f6f..c2376b6bd 100644 --- a/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java +++ b/plugins/openpgp-api-lib/openpgp-api/src/main/java/org/openintents/openpgp/AutocryptPeerUpdate.java @@ -51,7 +51,7 @@ public class AutocryptPeerUpdate implements Parcelable { } - public static AutocryptPeerUpdate createAutocryptPeerUpdate(byte[] keyData, Date timestamp, boolean isMutual) { + public static AutocryptPeerUpdate create(byte[] keyData, Date timestamp, boolean isMutual) { return new AutocryptPeerUpdate(keyData, timestamp, isMutual ? PreferEncrypt.MUTUAL : PreferEncrypt.NOPREFERENCE); } From 05361adfdef3a1a4b66aef6a2617bdc6d05f0a1b Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 27 Aug 2017 23:14:20 +0200 Subject: [PATCH 09/11] introduce named states in MessageCryptoHelper --- .../k9/ui/crypto/MessageCryptoHelper.java | 87 +++++++++++-------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java b/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java index b42f70f72..1afe63b5e 100644 --- a/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java +++ b/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java @@ -81,9 +81,7 @@ public class MessageCryptoHelper { private CryptoPart currentCryptoPart; private Intent currentCryptoResult; private Intent userInteractionResultIntent; - private boolean firstPassStarted; - private boolean secondPassStarted; - private boolean thirdPassStarted; + private State state; private CancelableBackgroundOperation cancelableBackgroundOperation; private boolean isCancelled; @@ -116,6 +114,7 @@ public class MessageCryptoHelper { } this.messageAnnotations = new MessageCryptoAnnotations(); + this.state = State.START; this.currentMessage = message; this.cachedDecryptionResult = cachedDecryptionResult; this.callback = callback; @@ -123,28 +122,26 @@ public class MessageCryptoHelper { nextStep(); } - private void findPartsForFirstPass() { - firstPassStarted = true; - + private void findPartsForEncryptionPass() { List encryptedParts = MessageDecryptVerifier.findEncryptedParts(currentMessage); processFoundEncryptedParts(encryptedParts); } - private void findPartsForSecondPass() { - secondPassStarted = true; - + private void findPartsForSignaturePass() { List signedParts = MessageDecryptVerifier.findSignedParts(currentMessage, messageAnnotations); processFoundSignedParts(signedParts); List inlineParts = MessageDecryptVerifier.findPgpInlineParts(currentMessage); - addFoundInlinePgpParts(inlineParts); + processFoundInlinePgpParts(inlineParts); } - private void findPartsForThirdPass() { - thirdPassStarted = true; + private void findPartsForAutocryptPass() { + boolean otherCryptoPerformed = !messageAnnotations.isEmpty(); + if (otherCryptoPerformed) { + return; + } - boolean noOtherCryptoPerformed = messageAnnotations.isEmpty(); - if (noOtherCryptoPerformed && autocryptOperations.hasAutocryptHeader(currentMessage)) { + if (autocryptOperations.hasAutocryptHeader(currentMessage)) { CryptoPart cryptoPart = new CryptoPart(CryptoPartType.PLAIN_AUTOCRYPT, currentMessage); partsToProcess.add(cryptoPart); } @@ -187,7 +184,7 @@ public class MessageCryptoHelper { messageAnnotations.put(part, annotation); } - private void addFoundInlinePgpParts(List foundParts) { + private void processFoundInlinePgpParts(List foundParts) { for (Part part : foundParts) { if (!currentMessage.getFlags().contains(Flag.X_DOWNLOADED_FULL)) { if (MessageDecryptVerifier.isPartPgpInlineEncrypted(part)) { @@ -209,13 +206,13 @@ public class MessageCryptoHelper { return; } - while (partsToProcess.isEmpty()) { - boolean hadNextPass = findPartsForNextPass(); + while (state != State.FINISHED && partsToProcess.isEmpty()) { + findPartsForNextPass(); + } - if (!hadNextPass) { - callbackReturnResult(); - return; - } + if (state == State.FINISHED) { + callbackReturnResult(); + return; } if (!isBoundToCryptoProviderService()) { @@ -642,21 +639,37 @@ public class MessageCryptoHelper { nextStep(); } - private boolean findPartsForNextPass() { - if (!firstPassStarted) { - findPartsForFirstPass(); - return true; - } - if (!secondPassStarted) { - findPartsForSecondPass(); - return true; - } - if (!thirdPassStarted) { - findPartsForThirdPass(); - return true; - } + private void findPartsForNextPass() { + switch (state) { + case START: { + state = State.ENCRYPTION; - return false; + findPartsForEncryptionPass(); + return; + } + + case ENCRYPTION: { + state = State.SIGNATURES; + + findPartsForSignaturePass(); + return; + } + case SIGNATURES: { + state = State.AUTOCRYPT; + + findPartsForAutocryptPass(); + return; + } + + case AUTOCRYPT: { + state = State.FINISHED; + return; + } + + default: { + throw new IllegalStateException("unhandled state"); + } + } } private void cleanupAfterProcessingFinished() { @@ -780,4 +793,8 @@ public class MessageCryptoHelper { return NO_REPLACEMENT_PART; } } + + private enum State { + START, ENCRYPTION, SIGNATURES, AUTOCRYPT, FINISHED + } } From 520bc2543de0047220988508b9d837a60e9d0468 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 27 Aug 2017 23:41:03 +0200 Subject: [PATCH 10/11] split up autocrypt operations class into package --- .../fsck/k9/autocrypt/AutocryptHeader.java | 31 ++++ .../k9/autocrypt/AutocryptHeaderParser.java | 97 +++++++++++ .../k9/autocrypt/AutocryptOperations.java | 45 ++++++ .../fsck/k9/crypto/AutocryptOperations.java | 150 ------------------ .../k9/ui/crypto/MessageCryptoHelper.java | 4 +- .../AutocryptHeaderParserTest.java} | 22 +-- 6 files changed, 186 insertions(+), 163 deletions(-) create mode 100644 k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptHeader.java create mode 100644 k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptHeaderParser.java create mode 100644 k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptOperations.java delete mode 100644 k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java rename k9mail/src/test/java/com/fsck/k9/{crypto/AutocryptOperationsTest.java => autocrypt/AutocryptHeaderParserTest.java} (80%) diff --git a/k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptHeader.java b/k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptHeader.java new file mode 100644 index 000000000..17a984799 --- /dev/null +++ b/k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptHeader.java @@ -0,0 +1,31 @@ +package com.fsck.k9.autocrypt; + + +import java.util.Map; + + +class AutocryptHeader { + static final String AUTOCRYPT_HEADER = "Autocrypt"; + + static final String AUTOCRYPT_PARAM_TO = "addr"; + static final String AUTOCRYPT_PARAM_KEY_DATA = "keydata"; + + static final String AUTOCRYPT_PARAM_TYPE = "type"; + static final String AUTOCRYPT_TYPE_1 = "1"; + + static final String AUTOCRYPT_PARAM_PREFER_ENCRYPT = "prefer-encrypt"; + static final String AUTOCRYPT_PREFER_ENCRYPT_MUTUAL = "mutual"; + + + final byte[] keyData; + final String addr; + final Map parameters; + final boolean isPreferEncryptMutual; + + AutocryptHeader(Map parameters, String addr, byte[] keyData, boolean isPreferEncryptMutual) { + this.parameters = parameters; + this.addr = addr; + this.keyData = keyData; + this.isPreferEncryptMutual = isPreferEncryptMutual; + } +} diff --git a/k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptHeaderParser.java b/k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptHeaderParser.java new file mode 100644 index 000000000..f42e83d38 --- /dev/null +++ b/k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptHeaderParser.java @@ -0,0 +1,97 @@ +package com.fsck.k9.autocrypt; + + +import java.util.ArrayList; +import java.util.Map; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.fsck.k9.mail.Message; +import com.fsck.k9.mail.internet.MimeUtility; +import okio.ByteString; +import timber.log.Timber; + + +class AutocryptHeaderParser { + private static final AutocryptHeaderParser INSTANCE = new AutocryptHeaderParser(); + + + public static AutocryptHeaderParser getInstance() { + return INSTANCE; + } + + private AutocryptHeaderParser() { } + + + @Nullable + AutocryptHeader getValidAutocryptHeader(Message currentMessage) { + String[] headers = currentMessage.getHeader(AutocryptHeader.AUTOCRYPT_HEADER); + ArrayList autocryptHeaders = parseAllAutocryptHeaders(headers); + + boolean isSingleValidHeader = autocryptHeaders.size() == 1; + return isSingleValidHeader ? autocryptHeaders.get(0) : null; + } + + @Nullable + private AutocryptHeader parseAutocryptHeader(String headerValue) { + Map parameters = MimeUtility.getAllHeaderParameters(headerValue); + + String type = parameters.remove(AutocryptHeader.AUTOCRYPT_PARAM_TYPE); + if (type != null && !type.equals(AutocryptHeader.AUTOCRYPT_TYPE_1)) { + Timber.e("autocrypt: unsupported type parameter %s", type); + return null; + } + + String base64KeyData = parameters.remove(AutocryptHeader.AUTOCRYPT_PARAM_KEY_DATA); + if (base64KeyData == null) { + Timber.e("autocrypt: missing key parameter"); + return null; + } + + ByteString byteString = ByteString.decodeBase64(base64KeyData); + if (byteString == null) { + Timber.e("autocrypt: error parsing base64 data"); + return null; + } + + String to = parameters.remove(AutocryptHeader.AUTOCRYPT_PARAM_TO); + if (to == null) { + Timber.e("autocrypt: no to header!"); + return null; + } + + boolean isPreferEncryptMutual = false; + String preferEncrypt = parameters.remove(AutocryptHeader.AUTOCRYPT_PARAM_PREFER_ENCRYPT); + if (AutocryptHeader.AUTOCRYPT_PREFER_ENCRYPT_MUTUAL.equalsIgnoreCase(preferEncrypt)) { + isPreferEncryptMutual = true; + } + + if (hasCriticalParameters(parameters)) { + return null; + } + + return new AutocryptHeader(parameters, to, byteString.toByteArray(), isPreferEncryptMutual); + } + + private boolean hasCriticalParameters(Map parameters) { + for (String parameterName : parameters.keySet()) { + if (parameterName != null && !parameterName.startsWith("_")) { + return true; + } + } + return false; + } + + @NonNull + private ArrayList parseAllAutocryptHeaders(String[] headers) { + ArrayList autocryptHeaders = new ArrayList<>(); + for (String header : headers) { + AutocryptHeader autocryptHeader = parseAutocryptHeader(header); + if (autocryptHeader != null) { + autocryptHeaders.add(autocryptHeader); + } + } + return autocryptHeaders; + } +} diff --git a/k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptOperations.java b/k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptOperations.java new file mode 100644 index 000000000..6cdc6baa5 --- /dev/null +++ b/k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptOperations.java @@ -0,0 +1,45 @@ +package com.fsck.k9.autocrypt; + + +import java.util.Date; + +import android.content.Intent; + +import com.fsck.k9.mail.Message; +import org.openintents.openpgp.AutocryptPeerUpdate; +import org.openintents.openpgp.util.OpenPgpApi; + + +public class AutocryptOperations { + private final AutocryptHeaderParser autocryptHeaderParser; + + public AutocryptOperations() { + this.autocryptHeaderParser = AutocryptHeaderParser.getInstance(); + } + + public boolean addAutocryptPeerUpdateToIntentIfPresent(Message currentMessage, Intent intent) { + AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(currentMessage); + if (autocryptHeader == null) { + return false; + } + + String messageFromAddress = currentMessage.getFrom()[0].getAddress(); + if (!autocryptHeader.addr.equalsIgnoreCase(messageFromAddress)) { + return false; + } + + Date messageDate = currentMessage.getSentDate(); + Date internalDate = currentMessage.getInternalDate(); + Date effectiveDate = messageDate.before(internalDate) ? messageDate : internalDate; + + AutocryptPeerUpdate data = AutocryptPeerUpdate.create( + autocryptHeader.keyData, effectiveDate, autocryptHeader.isPreferEncryptMutual); + intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID, messageFromAddress); + intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE, data); + return true; + } + + public boolean hasAutocryptHeader(Message currentMessage) { + return currentMessage.getHeader(AutocryptHeader.AUTOCRYPT_HEADER).length > 0; + } +} diff --git a/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java b/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java deleted file mode 100644 index 54e3afd07..000000000 --- a/k9mail/src/main/java/com/fsck/k9/crypto/AutocryptOperations.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.fsck.k9.crypto; - - -import java.util.ArrayList; -import java.util.Date; -import java.util.Map; - -import android.content.Intent; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; - -import com.fsck.k9.mail.Message; -import com.fsck.k9.mail.internet.MimeUtility; -import okio.ByteString; -import org.openintents.openpgp.AutocryptPeerUpdate; -import org.openintents.openpgp.util.OpenPgpApi; -import timber.log.Timber; - - -public class AutocryptOperations { - private static final String AUTOCRYPT_HEADER = "Autocrypt"; - - private static final String AUTOCRYPT_PARAM_TO = "addr"; - private static final String AUTOCRYPT_PARAM_KEY_DATA = "keydata"; - - private static final String AUTOCRYPT_PARAM_TYPE = "type"; - private static final String AUTOCRYPT_TYPE_1 = "1"; - - private static final String AUTOCRYPT_PARAM_PREFER_ENCRYPT = "prefer-encrypt"; - private static final String AUTOCRYPT_PREFER_ENCRYPT_MUTUAL = "mutual"; - - - public AutocryptOperations() { - } - - - public boolean addAutocryptPeerUpdateToIntentIfPresent(Message currentMessage, Intent intent) { - AutocryptHeader autocryptHeader = getValidAutocryptHeader(currentMessage); - if (autocryptHeader == null) { - return false; - } - - String messageFromAddress = currentMessage.getFrom()[0].getAddress(); - if (!autocryptHeader.addr.equalsIgnoreCase(messageFromAddress)) { - return false; - } - - Date messageDate = currentMessage.getSentDate(); - Date internalDate = currentMessage.getInternalDate(); - Date effectiveDate = messageDate.before(internalDate) ? messageDate : internalDate; - - AutocryptPeerUpdate data = AutocryptPeerUpdate.create( - autocryptHeader.keyData, effectiveDate, autocryptHeader.isPreferEncryptMutual); - intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_ID, messageFromAddress); - intent.putExtra(OpenPgpApi.EXTRA_AUTOCRYPT_PEER_UPDATE, data); - return true; - } - - @Nullable - @VisibleForTesting - AutocryptHeader getValidAutocryptHeader(Message currentMessage) { - String[] headers = currentMessage.getHeader(AUTOCRYPT_HEADER); - ArrayList autocryptHeaders = parseAllAutocryptHeaders(headers); - - boolean isSingleValidHeader = autocryptHeaders.size() == 1; - return isSingleValidHeader ? autocryptHeaders.get(0) : null; - } - - @NonNull - private ArrayList parseAllAutocryptHeaders(String[] headers) { - ArrayList autocryptHeaders = new ArrayList<>(); - for (String header : headers) { - AutocryptHeader autocryptHeader = parseAutocryptHeader(header); - if (autocryptHeader != null) { - autocryptHeaders.add(autocryptHeader); - } - } - return autocryptHeaders; - } - - @Nullable - private AutocryptHeader parseAutocryptHeader(String headerValue) { - Map parameters = MimeUtility.getAllHeaderParameters(headerValue); - - String type = parameters.remove(AUTOCRYPT_PARAM_TYPE); - if (type != null && !type.equals(AUTOCRYPT_TYPE_1)) { - Timber.e("autocrypt: unsupported type parameter %s", type); - return null; - } - - String base64KeyData = parameters.remove(AUTOCRYPT_PARAM_KEY_DATA); - if (base64KeyData == null) { - Timber.e("autocrypt: missing key parameter"); - return null; - } - - ByteString byteString = ByteString.decodeBase64(base64KeyData); - if (byteString == null) { - Timber.e("autocrypt: error parsing base64 data"); - return null; - } - - String to = parameters.remove(AUTOCRYPT_PARAM_TO); - if (to == null) { - Timber.e("autocrypt: no to header!"); - return null; - } - - boolean isPreferEncryptMutual = false; - String preferEncrypt = parameters.remove(AUTOCRYPT_PARAM_PREFER_ENCRYPT); - if (AUTOCRYPT_PREFER_ENCRYPT_MUTUAL.equalsIgnoreCase(preferEncrypt)) { - isPreferEncryptMutual = true; - } - - if (hasCriticalParameters(parameters)) { - return null; - } - - return new AutocryptHeader(parameters, to, byteString.toByteArray(), isPreferEncryptMutual); - } - - private boolean hasCriticalParameters(Map parameters) { - for (String parameterName : parameters.keySet()) { - if (parameterName != null && !parameterName.startsWith("_")) { - return true; - } - } - return false; - } - - public boolean hasAutocryptHeader(Message currentMessage) { - return currentMessage.getHeader(AUTOCRYPT_HEADER).length > 0; - } - - @VisibleForTesting - class AutocryptHeader { - final byte[] keyData; - final String addr; - final Map parameters; - final boolean isPreferEncryptMutual; - - private AutocryptHeader(Map parameters, String addr, byte[] keyData, boolean isPreferEncryptMutual) { - this.parameters = parameters; - this.addr = addr; - this.keyData = keyData; - this.isPreferEncryptMutual = isPreferEncryptMutual; - } - } -} diff --git a/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java b/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java index 1afe63b5e..05f587aab 100644 --- a/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java +++ b/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java @@ -18,7 +18,7 @@ import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import com.fsck.k9.K9; -import com.fsck.k9.crypto.AutocryptOperations; +import com.fsck.k9.autocrypt.AutocryptOperations; import com.fsck.k9.crypto.MessageDecryptVerifier; import com.fsck.k9.mail.Address; import com.fsck.k9.mail.Body; @@ -94,7 +94,7 @@ public class MessageCryptoHelper { this.context = context.getApplicationContext(); if (!K9.isOpenPgpProviderConfigured()) { - throw new IllegalStateException("MessageCryptoHelper must only be called with a openpgp provider!"); + throw new IllegalStateException("MessageCryptoHelper must only be called with a OpenPGP provider!"); } this.openPgpApiFactory = openPgpApiFactory; diff --git a/k9mail/src/test/java/com/fsck/k9/crypto/AutocryptOperationsTest.java b/k9mail/src/test/java/com/fsck/k9/autocrypt/AutocryptHeaderParserTest.java similarity index 80% rename from k9mail/src/test/java/com/fsck/k9/crypto/AutocryptOperationsTest.java rename to k9mail/src/test/java/com/fsck/k9/autocrypt/AutocryptHeaderParserTest.java index e9e11981b..fc1f0fc88 100644 --- a/k9mail/src/test/java/com/fsck/k9/crypto/AutocryptOperationsTest.java +++ b/k9mail/src/test/java/com/fsck/k9/autocrypt/AutocryptHeaderParserTest.java @@ -1,4 +1,4 @@ -package com.fsck.k9.crypto; +package com.fsck.k9.autocrypt; import java.io.FileInputStream; @@ -6,7 +6,6 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import com.fsck.k9.crypto.AutocryptOperations.AutocryptHeader; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.internet.BinaryTempFileBody; import com.fsck.k9.mail.internet.MimeMessage; @@ -24,8 +23,9 @@ import static org.junit.Assert.assertNull; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE, sdk = 21) -public class AutocryptOperationsTest { - AutocryptOperations autocryptOperations = new AutocryptOperations(); +@SuppressWarnings("WeakerAccess") +public class AutocryptHeaderParserTest { + AutocryptHeaderParser autocryptHeaderParser = AutocryptHeaderParser.getInstance(); @Before public void setUp() throws Exception { @@ -38,7 +38,7 @@ public class AutocryptOperationsTest { public void getValidAutocryptHeader__withNoHeader__shouldReturnNull() throws Exception { MimeMessage message = parseFromResource("autocrypt/no_autocrypt.eml"); - AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); + AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message); assertNull(autocryptHeader); } @@ -47,7 +47,7 @@ public class AutocryptOperationsTest { public void getValidAutocryptHeader__withBrokenBase64__shouldReturnNull() throws Exception { MimeMessage message = parseFromResource("autocrypt/rsa2048-broken-base64.eml"); - AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); + AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message); assertNull(autocryptHeader); } @@ -56,7 +56,7 @@ public class AutocryptOperationsTest { public void getValidAutocryptHeader__withSimpleAutocrypt() throws Exception { MimeMessage message = parseFromResource("autocrypt/rsa2048-simple.eml"); - AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); + AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message); assertNotNull(autocryptHeader); assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr); @@ -68,7 +68,7 @@ public class AutocryptOperationsTest { public void getValidAutocryptHeader__withExplicitType() throws Exception { MimeMessage message = parseFromResource("autocrypt/rsa2048-explicit-type.eml"); - AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); + AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message); assertNotNull(autocryptHeader); assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr); @@ -79,7 +79,7 @@ public class AutocryptOperationsTest { public void getValidAutocryptHeader__withUnknownType__shouldReturnNull() throws Exception { MimeMessage message = parseFromResource("autocrypt/unknown-type.eml"); - AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); + AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message); assertNull(autocryptHeader); } @@ -88,7 +88,7 @@ public class AutocryptOperationsTest { public void getValidAutocryptHeader__withUnknownCriticalHeader__shouldReturnNull() throws Exception { MimeMessage message = parseFromResource("autocrypt/rsa2048-unknown-critical.eml"); - AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); + AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message); assertNull(autocryptHeader); } @@ -97,7 +97,7 @@ public class AutocryptOperationsTest { public void getValidAutocryptHeader__withUnknownNonCriticalHeader() throws Exception { MimeMessage message = parseFromResource("autocrypt/rsa2048-unknown-non-critical.eml"); - AutocryptHeader autocryptHeader = autocryptOperations.getValidAutocryptHeader(message); + AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message); assertNotNull(autocryptHeader); assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr); From a41e75f5039e05bdddbcb1c02ebbf3d99f3ec394 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Tue, 29 Aug 2017 04:14:09 +0200 Subject: [PATCH 11/11] add autocrypt checks to MessageCryptoHelper tests --- .../fsck/k9/activity/MessageLoaderHelper.java | 4 +- .../k9/autocrypt/AutocryptOperations.java | 11 ++++- .../k9/ui/crypto/MessageCryptoHelper.java | 5 ++- .../k9/ui/crypto/MessageCryptoHelperTest.java | 45 ++++++++++++++++++- 4 files changed, 59 insertions(+), 6 deletions(-) diff --git a/k9mail/src/main/java/com/fsck/k9/activity/MessageLoaderHelper.java b/k9mail/src/main/java/com/fsck/k9/activity/MessageLoaderHelper.java index 70248f61c..54787eb63 100644 --- a/k9mail/src/main/java/com/fsck/k9/activity/MessageLoaderHelper.java +++ b/k9mail/src/main/java/com/fsck/k9/activity/MessageLoaderHelper.java @@ -14,6 +14,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; +import com.fsck.k9.autocrypt.AutocryptOperations; import com.fsck.k9.ui.crypto.OpenPgpApiFactory; import timber.log.Timber; @@ -270,7 +271,8 @@ public class MessageLoaderHelper { messageCryptoHelper = retainCryptoHelperFragment.getData(); } if (messageCryptoHelper == null || messageCryptoHelper.isConfiguredForOutdatedCryptoProvider()) { - messageCryptoHelper = new MessageCryptoHelper(context, new OpenPgpApiFactory()); + messageCryptoHelper = new MessageCryptoHelper( + context, new OpenPgpApiFactory(), AutocryptOperations.getInstance()); retainCryptoHelperFragment.setData(messageCryptoHelper); } messageCryptoHelper.asyncStartOrResumeProcessingMessage( diff --git a/k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptOperations.java b/k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptOperations.java index 6cdc6baa5..fc578d8f1 100644 --- a/k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptOperations.java +++ b/k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptOperations.java @@ -13,8 +13,15 @@ import org.openintents.openpgp.util.OpenPgpApi; public class AutocryptOperations { private final AutocryptHeaderParser autocryptHeaderParser; - public AutocryptOperations() { - this.autocryptHeaderParser = AutocryptHeaderParser.getInstance(); + + public static AutocryptOperations getInstance() { + AutocryptHeaderParser autocryptHeaderParser = AutocryptHeaderParser.getInstance(); + return new AutocryptOperations(autocryptHeaderParser); + } + + + private AutocryptOperations(AutocryptHeaderParser autocryptHeaderParser) { + this.autocryptHeaderParser = autocryptHeaderParser; } public boolean addAutocryptPeerUpdateToIntentIfPresent(Message currentMessage, Intent intent) { diff --git a/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java b/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java index 05f587aab..751c76853 100644 --- a/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java +++ b/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java @@ -90,15 +90,16 @@ public class MessageCryptoHelper { private OpenPgpApiFactory openPgpApiFactory; - public MessageCryptoHelper(Context context, OpenPgpApiFactory openPgpApiFactory) { + public MessageCryptoHelper(Context context, OpenPgpApiFactory openPgpApiFactory, + AutocryptOperations autocryptOperations) { this.context = context.getApplicationContext(); if (!K9.isOpenPgpProviderConfigured()) { throw new IllegalStateException("MessageCryptoHelper must only be called with a OpenPGP provider!"); } + this.autocryptOperations = autocryptOperations; this.openPgpApiFactory = openPgpApiFactory; - autocryptOperations = new AutocryptOperations(); openPgpProviderPackage = K9.getOpenPgpProvider(); } diff --git a/k9mail/src/test/java/com/fsck/k9/ui/crypto/MessageCryptoHelperTest.java b/k9mail/src/test/java/com/fsck/k9/ui/crypto/MessageCryptoHelperTest.java index 8915b62e3..6f102d79c 100644 --- a/k9mail/src/test/java/com/fsck/k9/ui/crypto/MessageCryptoHelperTest.java +++ b/k9mail/src/test/java/com/fsck/k9/ui/crypto/MessageCryptoHelperTest.java @@ -1,6 +1,7 @@ package com.fsck.k9.ui.crypto; +import java.io.InputStream; import java.io.OutputStream; import android.app.PendingIntent; @@ -8,6 +9,7 @@ import android.content.Context; import android.content.Intent; import com.fsck.k9.K9; +import com.fsck.k9.autocrypt.AutocryptOperations; import com.fsck.k9.mail.Address; import com.fsck.k9.mail.Body; import com.fsck.k9.mail.BodyPart; @@ -27,6 +29,7 @@ import org.openintents.openpgp.IOpenPgpService2; import org.openintents.openpgp.OpenPgpDecryptionResult; import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.util.OpenPgpApi; +import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback; import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpSinkResultCallback; import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSink; import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSource; @@ -38,6 +41,8 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertSame; import static junit.framework.Assert.assertTrue; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -54,17 +59,21 @@ public class MessageCryptoHelperTest { private Intent capturedApiIntent; private IOpenPgpSinkResultCallback capturedCallback; private MessageCryptoCallback messageCryptoCallback; + private AutocryptOperations autocryptOperations; @Before public void setUp() throws Exception { openPgpApi = mock(OpenPgpApi.class); + autocryptOperations = mock(AutocryptOperations.class); K9.setOpenPgpProvider("org.example.dummy"); OpenPgpApiFactory openPgpApiFactory = mock(OpenPgpApiFactory.class); when(openPgpApiFactory.createOpenPgpApi(any(Context.class), any(IOpenPgpService2.class))).thenReturn(openPgpApi); - messageCryptoHelper = new MessageCryptoHelper(RuntimeEnvironment.application, openPgpApiFactory); + + messageCryptoHelper = new MessageCryptoHelper(RuntimeEnvironment.application, openPgpApiFactory, + autocryptOperations); messageCryptoCallback = mock(MessageCryptoCallback.class); } @@ -82,6 +91,35 @@ public class MessageCryptoHelperTest { MessageCryptoAnnotations annotations = captor.getValue(); assertTrue(annotations.isEmpty()); verifyNoMoreInteractions(messageCryptoCallback); + + verify(autocryptOperations).hasAutocryptHeader(message); + verifyNoMoreInteractions(autocryptOperations); + } + + @Test + public void textPlain_withAutocrypt() throws Exception { + MimeMessage message = new MimeMessage(); + message.setUid("msguid"); + message.setHeader("Content-Type", "text/plain"); + + when(autocryptOperations.hasAutocryptHeader(message)).thenReturn(true); + when(autocryptOperations.addAutocryptPeerUpdateToIntentIfPresent(same(message), any(Intent.class))).thenReturn(true); + + + MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class); + messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null); + + + ArgumentCaptor captor = ArgumentCaptor.forClass(MessageCryptoAnnotations.class); + verify(messageCryptoCallback).onCryptoOperationsFinished(captor.capture()); + MessageCryptoAnnotations annotations = captor.getValue(); + assertTrue(annotations.isEmpty()); + verifyNoMoreInteractions(messageCryptoCallback); + + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(autocryptOperations).addAutocryptPeerUpdateToIntentIfPresent(same(message), intentCaptor.capture()); + verify(openPgpApi).executeApiAsync(same(intentCaptor.getValue()), same((InputStream) null), + same((OutputStream) null), any(IOpenPgpCallback.class)); } @Test @@ -161,6 +199,9 @@ public class MessageCryptoHelperTest { assertEquals(OpenPgpApi.ACTION_DECRYPT_VERIFY, capturedApiIntent.getAction()); assertEquals("test@example.org", capturedApiIntent.getStringExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS)); + + verify(autocryptOperations).addAutocryptPeerUpdateToIntentIfPresent(message, capturedApiIntent); + verifyNoMoreInteractions(autocryptOperations); } @Test @@ -201,6 +242,8 @@ public class MessageCryptoHelperTest { assertEquals("test@example.org", capturedApiIntent.getStringExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS)); assertPartAnnotationHasState(message, messageCryptoCallback, CryptoError.OPENPGP_OK, decryptedPart, decryptionResult, signatureResult, pendingIntent); + verify(autocryptOperations).addAutocryptPeerUpdateToIntentIfPresent(message, capturedApiIntent); + verifyNoMoreInteractions(autocryptOperations); } private void processEncryptedMessageAndCaptureMocks(Message message, Body encryptedBody, OutputStream outputStream)