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/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/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..fc578d8f1 --- /dev/null +++ b/k9mail/src/main/java/com/fsck/k9/autocrypt/AutocryptOperations.java @@ -0,0 +1,52 @@ +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 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) { + 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/message/PgpMessageBuilder.java b/k9mail/src/main/java/com/fsck/k9/message/PgpMessageBuilder.java index 46366b0b0..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 { @@ -154,7 +153,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/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java b/k9mail/src/main/java/com/fsck/k9/ui/crypto/MessageCryptoHelper.java index 59550fd95..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 @@ -18,6 +18,7 @@ import android.support.annotation.Nullable; import android.support.annotation.WorkerThread; import com.fsck.k9.K9; +import com.fsck.k9.autocrypt.AutocryptOperations; import com.fsck.k9.crypto.MessageDecryptVerifier; import com.fsck.k9.mail.Address; import com.fsck.k9.mail.Body; @@ -45,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; @@ -62,8 +64,9 @@ 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<>(); + private final Deque partsToProcess = new ArrayDeque<>(); @Nullable private MessageCryptoCallback callback; @@ -78,7 +81,7 @@ public class MessageCryptoHelper { private CryptoPart currentCryptoPart; private Intent currentCryptoResult; private Intent userInteractionResultIntent; - private boolean secondPassStarted; + private State state; private CancelableBackgroundOperation cancelableBackgroundOperation; private boolean isCancelled; @@ -87,13 +90,15 @@ 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!"); + throw new IllegalStateException("MessageCryptoHelper must only be called with a OpenPGP provider!"); } + this.autocryptOperations = autocryptOperations; this.openPgpApiFactory = openPgpApiFactory; openPgpProviderPackage = K9.getOpenPgpProvider(); } @@ -110,28 +115,37 @@ public class MessageCryptoHelper { } this.messageAnnotations = new MessageCryptoAnnotations(); + this.state = State.START; this.currentMessage = message; this.cachedDecryptionResult = cachedDecryptionResult; this.callback = callback; - runFirstPass(); + nextStep(); } - private void runFirstPass() { + private void findPartsForEncryptionPass() { List encryptedParts = MessageDecryptVerifier.findEncryptedParts(currentMessage); processFoundEncryptedParts(encryptedParts); - - decryptOrVerifyNextPart(); } - private void runSecondPass() { + private void findPartsForSignaturePass() { List signedParts = MessageDecryptVerifier.findSignedParts(currentMessage, messageAnnotations); processFoundSignedParts(signedParts); List inlineParts = MessageDecryptVerifier.findPgpInlineParts(currentMessage); - addFoundInlinePgpParts(inlineParts); + processFoundInlinePgpParts(inlineParts); + } - decryptOrVerifyNextPart(); + private void findPartsForAutocryptPass() { + boolean otherCryptoPerformed = !messageAnnotations.isEmpty(); + if (otherCryptoPerformed) { + return; + } + + if (autocryptOperations.hasAutocryptHeader(currentMessage)) { + CryptoPart cryptoPart = new CryptoPart(CryptoPartType.PLAIN_AUTOCRYPT, currentMessage); + partsToProcess.add(cryptoPart); + } } private void processFoundEncryptedParts(List foundParts) { @@ -142,7 +156,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()); @@ -158,7 +172,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); @@ -171,7 +185,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)) { @@ -184,29 +198,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()) { - runSecondPassOrReturnResultToFragment(); + while (state != State.FINISHED && partsToProcess.isEmpty()) { + findPartsForNextPass(); + } + + if (state == State.FINISHED) { + callbackReturnResult(); return; } - CryptoPart cryptoPart = partsToDecryptOrVerify.peekFirst(); - startDecryptingOrVerifyingPart(cryptoPart); - } - - private void startDecryptingOrVerifyingPart(CryptoPart cryptoPart) { if (!isBoundToCryptoProviderService()) { connectToCryptoProviderService(); + return; + } + + currentCryptoPart = partsToProcess.peekFirst(); + if (currentCryptoPart.type == CryptoPartType.PLAIN_AUTOCRYPT) { + processAutocryptHeaderForCurrentPart(); } else { - decryptOrVerifyPart(cryptoPart); + decryptOrVerifyCurrentPart(); } } @@ -222,7 +241,7 @@ public class MessageCryptoHelper { public void onBound(IOpenPgpService2 service) { openPgpApi = openPgpApiFactory.createOpenPgpApi(context, service); - decryptOrVerifyNextPart(); + nextStep(); } @Override @@ -234,24 +253,26 @@ 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 = getDecryptionIntent(); + if (apiIntent == null) { + apiIntent = getDecryptVerifyIntent(); } - decryptVerify(decryptIntent); + decryptVerify(apiIntent); } @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); @@ -259,22 +280,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); + 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); @@ -285,6 +308,23 @@ public class MessageCryptoHelper { } } + 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(); + } + private void callAsyncInlineOperation(Intent intent) throws IOException { OpenPgpDataSource dataSource = getDataSourceForEncryptedOrInlineData(); OpenPgpDataSink dataSink = getDataSinkForDecryptedInlineData(); @@ -534,7 +574,7 @@ public class MessageCryptoHelper { } if (resultCode == Activity.RESULT_OK) { userInteractionResultIntent = data; - decryptOrVerifyNextPart(); + nextStep(); } else { onCryptoOperationCanceled(); } @@ -586,31 +626,55 @@ 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 runSecondPassOrReturnResultToFragment() { - if (secondPassStarted) { - callbackReturnResult(); - return; + private void findPartsForNextPass() { + switch (state) { + case START: { + state = State.ENCRYPTION; + + 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"); + } } - secondPassStarted = true; - runSecondPass(); } private void cleanupAfterProcessingFinished() { - partsToDecryptOrVerify.clear(); + partsToProcess.clear(); openPgpApi = null; if (openPgpServiceConnection != null) { openPgpServiceConnection.unbindFromService(); @@ -699,7 +763,8 @@ public class MessageCryptoHelper { private enum CryptoPartType { PGP_INLINE, PGP_ENCRYPTED, - PGP_SIGNED + PGP_SIGNED, + PLAIN_AUTOCRYPT } @Nullable @@ -729,4 +794,8 @@ public class MessageCryptoHelper { return NO_REPLACEMENT_PART; } } + + private enum State { + START, ENCRYPTION, SIGNATURES, AUTOCRYPT, FINISHED + } } diff --git a/k9mail/src/test/java/com/fsck/k9/autocrypt/AutocryptHeaderParserTest.java b/k9mail/src/test/java/com/fsck/k9/autocrypt/AutocryptHeaderParserTest.java new file mode 100644 index 000000000..fc1f0fc88 --- /dev/null +++ b/k9mail/src/test/java/com/fsck/k9/autocrypt/AutocryptHeaderParserTest.java @@ -0,0 +1,118 @@ +package com.fsck.k9.autocrypt; + + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +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) +@SuppressWarnings("WeakerAccess") +public class AutocryptHeaderParserTest { + AutocryptHeaderParser autocryptHeaderParser = AutocryptHeaderParser.getInstance(); + + @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 = autocryptHeaderParser.getValidAutocryptHeader(message); + + assertNull(autocryptHeader); + } + + @Test + public void getValidAutocryptHeader__withBrokenBase64__shouldReturnNull() throws Exception { + MimeMessage message = parseFromResource("autocrypt/rsa2048-broken-base64.eml"); + + AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message); + + assertNull(autocryptHeader); + } + + @Test + public void getValidAutocryptHeader__withSimpleAutocrypt() throws Exception { + MimeMessage message = parseFromResource("autocrypt/rsa2048-simple.eml"); + + AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message); + + assertNotNull(autocryptHeader); + assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr); + 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 = autocryptHeaderParser.getValidAutocryptHeader(message); + + assertNotNull(autocryptHeader); + assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr); + assertEquals(0, autocryptHeader.parameters.size()); + } + + @Test + public void getValidAutocryptHeader__withUnknownType__shouldReturnNull() throws Exception { + MimeMessage message = parseFromResource("autocrypt/unknown-type.eml"); + + AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message); + + assertNull(autocryptHeader); + } + + @Test + public void getValidAutocryptHeader__withUnknownCriticalHeader__shouldReturnNull() throws Exception { + MimeMessage message = parseFromResource("autocrypt/rsa2048-unknown-critical.eml"); + + AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message); + + assertNull(autocryptHeader); + } + + @Test + public void getValidAutocryptHeader__withUnknownNonCriticalHeader() throws Exception { + MimeMessage message = parseFromResource("autocrypt/rsa2048-unknown-non-critical.eml"); + + AutocryptHeader autocryptHeader = autocryptHeaderParser.getValidAutocryptHeader(message); + + assertNotNull(autocryptHeader); + assertEquals("alice@testsuite.autocrypt.org", autocryptHeader.addr); + 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/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/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) 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..b2a984d0a --- /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 +Autocrypt: addr=alice@testsuite.autocrypt.org; keydata= + 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..72a6b95e6 --- /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 +Autocrypt: addr=alice@testsuite.autocrypt.org; type=1; keydata= + 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..09241ab37 --- /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; keydata= + 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..56417bc11 --- /dev/null +++ b/k9mail/src/test/resources/autocrypt/rsa2048-simple.eml @@ -0,0 +1,34 @@ +From: Alice +To: Bob +Subject: an INBOME RSA test +Autocrypt: addr=alice@testsuite.autocrypt.org; keydata= + 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..05edf2437 --- /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 +Autocrypt: addr=alice@testsuite.autocrypt.org; danger=do-not-use; keydata= + 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..f5e046be7 --- /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 +Autocrypt: addr=alice@testsuite.autocrypt.org; _monkey=ignore; keydata= + 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..bf79de092 --- /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 +Autocrypt: addr=alice@testsuite.autocrypt.org; type=x; keydata= + 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). 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..c2376b6bd --- /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 create(byte[] keyData, Date timestamp, boolean isMutual) { + return new AutocryptPeerUpdate(keyData, timestamp, isMutual ? PreferEncrypt.MUTUAL : 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";